<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" >
  <link rel="dns-prefetch" href="https://gitee.com/jietuo/blog.git">
  <title>个人博客</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="Talk is cheap. Show me the code.">
<meta property="og:type" content="website">
<meta property="og:title" content="个人博客">
<meta property="og:url" content="https:&#x2F;&#x2F;gitee.com&#x2F;jietuo&#x2F;blog.git&#x2F;page&#x2F;3&#x2F;index.html">
<meta property="og:site_name" content="个人博客">
<meta property="og:description" content="Talk is cheap. Show me the code.">
<meta property="og:locale" content="en">
<meta name="twitter:card" content="summary">
  
    <link rel="alternative" href="/atom.xml" title="个人博客" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  <link rel="stylesheet" type="text/css" href="/blog/./main.0cf68a.css">
  <style type="text/css">
  
    #container.show {
      background: linear-gradient(200deg,#a0cfe4,#e8c37e);
    }
  </style>
  

  

</head>

<body>
  <div id="container" q-class="show:isCtnShow">
    <canvas id="anm-canvas" class="anm-canvas"></canvas>
    <div class="left-col" q-class="show:isShow">
      
<div class="overlay" style="background: #4d4d4d"></div>
<div class="intrude-less">
	<header id="header" class="inner">
		<a href="/" class="profilepic">
			<img src="./source/images/head.jpeg" class="js-avatar">
		</a>
		<hgroup>
		  <h1 class="header-author"><a href="/">zhangxu</a></h1>
		</hgroup>
		
		<p class="header-subtitle">Talk is cheap. Show me the code.</p>
		

		<nav class="header-menu">
			<ul>
			
				<li><a href="/blog/">主页</a></li>
	        
				<li><a href="/blog/tags/%E9%9A%8F%E7%AC%94/">随笔</a></li>
	        
			</ul>
		</nav>
		<nav class="header-smart-menu">
    		
    			
    			<a q-on="click: openSlider(e, 'innerArchive')" href="javascript:void(0)" target="_blank" rel="noopener">所有文章</a>
    			
            
    			
    			<a q-on="click: openSlider(e, 'friends')" href="javascript:void(0)" target="_blank" rel="noopener">友链</a>
    			
            
    			
    			<a q-on="click: openSlider(e, 'aboutme')" href="javascript:void(0)" target="_blank" rel="noopener">关于我</a>
    			
            
		</nav>
		<nav class="header-nav">
			<div class="social">
				
					<a class="github" target="_blank" href="#" title="github"><i class="icon-github"></i></a>
		        
					<a class="weibo" target="_blank" href="#" title="weibo"><i class="icon-weibo"></i></a>
		        
					<a class="rss" target="_blank" href="#" title="rss"><i class="icon-rss"></i></a>
		        
					<a class="zhihu" target="_blank" href="#" title="zhihu"><i class="icon-zhihu"></i></a>
		        
			</div>
		</nav>
	</header>		
</div>

    </div>
    <div class="mid-col" q-class="show:isShow,hide:isShow|isFalse">
      
<nav id="mobile-nav">
  	<div class="overlay js-overlay" style="background: #4d4d4d"></div>
	<div class="btnctn js-mobile-btnctn">
  		<div class="slider-trigger list" q-on="click: openSlider(e)"><i class="icon icon-sort"></i></div>
	</div>
	<div class="intrude-less">
		<header id="header" class="inner">
			<div class="profilepic">
				<img src="./source/images/head.jpeg" class="js-avatar">
			</div>
			<hgroup>
			  <h1 class="header-author js-header-author">zhangxu</h1>
			</hgroup>
			
			<p class="header-subtitle"><i class="icon icon-quo-left"></i>Talk is cheap. Show me the code.<i class="icon icon-quo-right"></i></p>
			
			
			
				
			
				
			
			
			
			<nav class="header-nav">
				<div class="social">
					
						<a class="github" target="_blank" href="#" title="github"><i class="icon-github"></i></a>
			        
						<a class="weibo" target="_blank" href="#" title="weibo"><i class="icon-weibo"></i></a>
			        
						<a class="rss" target="_blank" href="#" title="rss"><i class="icon-rss"></i></a>
			        
						<a class="zhihu" target="_blank" href="#" title="zhihu"><i class="icon-zhihu"></i></a>
			        
				</div>
			</nav>

			<nav class="header-menu js-header-menu">
				<ul style="width: 50%">
				
				
					<li style="width: 50%"><a href="/blog/">主页</a></li>
		        
					<li style="width: 50%"><a href="/blog/tags/%E9%9A%8F%E7%AC%94/">随笔</a></li>
		        
				</ul>
			</nav>
		</header>				
	</div>
	<div class="mobile-mask" style="display:none" q-show="isShow"></div>
</nav>

      <div id="wrapper" class="body-wrap">
        <div class="menu-l">
          <div class="canvas-wrap">
            <canvas data-colors="#eaeaea" data-sectionHeight="100" data-contentId="js-content" id="myCanvas1" class="anm-canvas"></canvas>
          </div>
          <div id="js-content" class="content-ll">
            
  
    <article id="post-极客时间/Java性能调优实战/26 _ 答疑课堂：模块四热点问题解答" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/26%20_%20%E7%AD%94%E7%96%91%E8%AF%BE%E5%A0%82%EF%BC%9A%E6%A8%A1%E5%9D%97%E5%9B%9B%E7%83%AD%E7%82%B9%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94/">极客时间/Java性能调优实战/26 _ 答疑课堂：模块四热点问题解答</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/26%20_%20%E7%AD%94%E7%96%91%E8%AF%BE%E5%A0%82%EF%BC%9A%E6%A8%A1%E5%9D%97%E5%9B%9B%E7%83%AD%E7%82%B9%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:48.612Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="26 _ 答疑课堂：模块四热点问题解答" src="https://static001.geekbang.org/resource/audio/0c/80/0cfb0354c0f56d7debb1b06a39fa2080.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>本周我们结束了“JVM性能监测及调优”的学习，这一期答疑课堂我精选了模块四中 11 位同学的留言，进行集中解答，希望也能对你有所帮助。另外，我想为坚持跟到现在的同学点个赞，期待我们能有更多的技术交流，共同成长。</p><h2><a href="https://time.geekbang.org/column/article/106203" target="_blank" rel="noopener">第20讲</a></h2><p><img src="https://static001.geekbang.org/resource/image/31/3a/31a205290c3b2391f115ee77f511a43a.jpeg?wh=1125*1306" alt=""></p><p>很多同学都问到了类似“黑夜里的猫"问到的问题，所以我来集中回复一下。JVM的内存模型只是一个规范，方法区也是一个规范，一个逻辑分区，并不是一个物理空间，我们这里说的字符串常量放在堆内存空间中，是指实际的物理空间。</p><p><img src="https://static001.geekbang.org/resource/image/2a/01/2ac5ee0c9a6fe67ce8f896be75d05f01.jpeg?wh=1125*1218" alt=""></p><p>文灏的问题和上一个类似，一同回复一下。元空间是属于方法区的，方法区只是一个逻辑分区，而元空间是具体实现。所以类的元数据是存放在元空间，逻辑上属于方法区。</p><h2><a href="https://time.geekbang.org/column/article/106953" target="_blank" rel="noopener">第21讲</a></h2><p><img src="https://static001.geekbang.org/resource/image/f2/76/f2fa07e388f5a3dbe84bb12bfea5ee76.jpeg?wh=1125*1164" alt=""></p><p>Liam同学，目前Hotspot虚拟机暂时不支持栈上分配对象。W.LI同学的留言值得参考，所以这里一同贴出来了。</p><p><img src="https://static001.geekbang.org/resource/image/20/f2/20e59cb2df51bd171d41c81e074821f2.jpeg?wh=1080*2566" alt=""></p><h2><a href="https://time.geekbang.org/column/article/107396" target="_blank" rel="noopener">第22讲</a></h2><p><img src="https://static001.geekbang.org/resource/image/09/25/09ada15236e8ceeef2558d6ab7505425.jpeg?wh=1125*4041" alt=""></p><p>非常赞，Region这块，Jxin同学讲解得很到位。这里我再总结下CMS和G1的一些知识点。</p><p>CMS垃圾收集器是基于标记清除算法实现的，目前主要用于老年代垃圾回收。CMS收集器的GC周期主要由7个阶段组成，其中有两个阶段会发生stop-the-world，其它阶段都是并发执行的。</p><!-- [[[read_end]]] --><p><img src="https://static001.geekbang.org/resource/image/50/aa/500c2f0e112ced378fd49a09c61c5caa.jpg?wh=1276*1132" alt=""></p><p>G1垃圾收集器是基于标记整理算法实现的，是一个分代垃圾收集器，既负责年轻代，也负责老年代的垃圾回收。</p><p>跟之前各个分代使用连续的虚拟内存地址不一样，G1使用了一种  Region  方式对堆内存进行了划分，同样也分年轻代、老年代，但每一代使用的是N个不连续的Region内存块，每个Region占用一块连续的虚拟内存地址。</p><p>在G1中，还有一种叫 Humongous  区域，用于存储特别大的对象。G1内部做了一个优化，一旦发现没有引用指向巨型对象，则可直接在年轻代的YoungGC中被回收掉。</p><p><img src="https://static001.geekbang.org/resource/image/f8/be/f832278afd5cdb94decd1f6826056dbe.jpg?wh=868*578" alt=""></p><p>G1分为Young GC、Mix GC以及Full GC。</p><p>G1 Young GC主要是在Eden区进行，当Eden区空间不足时，则会触发一次Young GC。将Eden区数据移到Survivor空间时，如果Survivor空间不足，则会直接晋升到老年代。此时Survivor的数据也会晋升到老年代。Young GC的执行是并行的，期间会发生STW。</p><p>当堆空间的占用率达到一定阈值后会触发G1 Mix GC（阈值由命令参数-XX:InitiatingHeapOccupancyPercent设定，默认值45），Mix GC主要包括了四个阶段，其中只有并发标记阶段不会发生STW，其它阶段均会发生STW。</p><p><img src="https://static001.geekbang.org/resource/image/b8/2f/b8090ff2c7ddf54fb5f6e3c19a36d32f.jpg?wh=2536*1032" alt=""></p><p>G1和CMS主要的区别在于：</p><ul>
<li>CMS主要集中在老年代的回收，而G1集中在分代回收，包括了年轻代的Young GC以及老年代的Mix GC；</li>
<li>G1使用了Region方式对堆内存进行了划分，且基于标记整理算法实现，整体减少了垃圾碎片的产生；</li>
<li>在初始化标记阶段，搜索可达对象使用到的Card Table，其实现方式不一样。</li>
</ul><p>这里我简单解释下Card Table，在垃圾回收的时候都是从Root开始搜索，这会先经过年轻代再到老年代，也有可能老年代引用到年轻代对象，如果发生Young GC，除了从年轻代扫描根对象之外，还需要再从老年代扫描根对象，确认引用年轻代对象的情况。</p><p><strong>这种属于跨代处理，非常消耗性能。</strong>为了避免在回收年轻代时跨代扫描整个老年代，CMS和G1都用到了Card Table来记录这些引用关系。只是G1在Card Table的基础上引入了RSet，每个Region初始化时，都会初始化一个RSet，RSet记录了其它Region中的对象引用本Region对象的关系。</p><p>除此之外，CMS和G1在解决并发标记时漏标的方式也不一样，CMS使用的是Incremental Update算法，而G1使用的是SATB算法。</p><p>首先，我们要了解在并发标记中，G1和CMS都是基于三色标记算法来实现的：</p><ul>
<li>黑色：根对象，或者对象和对象中的子对象都被扫描；</li>
<li>灰色：对象本身被扫描，但还没扫描对象中的子对象；</li>
<li>白色：不可达对象。</li>
</ul><p>基于这种标记有一个漏标的问题，也就是说，当一个白色标记对象，在垃圾回收被清理掉时，正好有一个对象引用了该白色标记对象，此时由于被回收掉了，就会出现对象丢失的问题。</p><p>为了避免上述问题，CMS采用了Incremental Update算法，只要在写屏障（write barrier）里发现一个白对象的引用被赋值到一个黑对象的字段里，那就把这个白对象变成灰色的。而在G1中，采用的是SATB算法，该算法认为开始时所有能遍历到的对象都是需要标记的，即认为都是活的。</p><p>G1具备Pause Prediction Model ，即停顿预测模型。用户可以设定整个GC过程中期望的停顿时间，用参数-XX:MaxGCPauseMillis可以指定一个G1收集过程的目标停顿时间，默认值200ms。</p><p>G1会根据这个模型统计出来的历史数据，来预测一次垃圾回收所需要的Region数量，通过控制Region数来控制目标停顿时间的实现。</p><p><img src="https://static001.geekbang.org/resource/image/91/b4/915e9793981a278112087f0c880b96b4.jpeg?wh=1125*1386" alt=""></p><p>Liam提出的这两个问题都非常好。</p><p>不管什么GC，都会发送stop-the-world，区别是发生的时间长短。而这个时间跟垃圾收集器又有关系，Serial、PartNew、Parallel Scavenge收集器无论是串行还是并行，都会挂起用户线程，而CMS和G1在并发标记时，是不会挂起用户线程的，但其它时候一样会挂起用户线程，stop the world 的时间相对来说就小很多了。</p><p>Major Gc 在很多参考资料中是等价于 Full GC的，我们也可以发现很多性能监测工具中只有Minor GC 和 Full GC。一般情况下，一次Full GC将会对年轻代、老年代、元空间以及堆外内存进行垃圾回收。触发Full GC的原因有很多：</p><ul>
<li>当年轻代晋升到老年代的对象大小，并比目前老年代剩余的空间大小还要大时，会触发Full GC；</li>
<li>当老年代的空间使用率超过某阈值时，会触发Full GC；</li>
<li>当元空间不足时（JDK1.7永久代不足），也会触发Full GC；</li>
<li>当调用System.gc()也会安排一次Full GC。</li>
</ul><p><img src="https://static001.geekbang.org/resource/image/a8/24/a8a506a512922609669b4073d0dbc224.jpeg?wh=1125*2314" alt=""></p><p>接下来解答 ninghtmare 的提问。我们可以通过  jstat -gc pid interval  查看每次GC之后，具体每一个分区的内存使用率变化情况。我们可以通过JVM的设置参数，来查看垃圾收集器的具体设置参数，使用的方式有很多，例如  jcmd pid VM.flags 就可以查看到相关的设置参数。</p><p><img src="https://static001.geekbang.org/resource/image/26/2e/26d688a3af534fb00fe3b89d261e5c2e.jpg?wh=1926*190" alt=""></p><p>这里附上第22讲中，我总结的各个设置参数对应的垃圾收集器图表。</p><p><img src="https://static001.geekbang.org/resource/image/e2/56/e29c9ac3e53ffbc8a5648644a87d6256.jpeg?wh=1896*702" alt=""></p><h2><a href="https://time.geekbang.org/column/article/108139" target="_blank" rel="noopener">第23讲</a></h2><p><img src="https://static001.geekbang.org/resource/image/bb/76/bb92ec845c715f9d36a6ce48a0c7d276.jpeg?wh=1125*2227" alt=""></p><p>我又不乱来同学的留言真是没有乱来，细节掌握得很好！</p><p>前提是老年代有足够接受这些对象的空间，才会进行分配担保。如果老年代剩余空间小于每次Minor GC晋升到老年代的平均值，则会发起一次  Full GC。</p><p><img src="https://static001.geekbang.org/resource/image/28/20/2838514b87e62d69bf51d7a7f12a0c20.jpeg?wh=1125*2271" alt=""></p><p>看到这里，我发现爱提问的同学始终爱提问，非常鼓励啊，技术是需要交流的，也欢迎你有任何疑问，随时留言给我，我会知无不尽。</p><p>现在回答W.LI同学的问题。这个会根据我们创建对象占用的内存使用率，合理分配内存，并不仅仅考虑对象晋升的问题，还会综合考虑回收停顿时间等因素。针对某些特殊场景，我们可以手动来调优配置。</p><h2><a href="https://time.geekbang.org/column/article/108582" target="_blank" rel="noopener">第24讲</a></h2><p><img src="https://static001.geekbang.org/resource/image/10/24/1080a8574a1a1ded35b736ccbec40524.jpeg?wh=1125*1164" alt=""></p><p>下面解答Geek_75b4cd同学的问题。</p><p>我们知道，ThreadLocal是基于ThreadLocalMap实现的，这个Map的Entry继承了WeakReference，而Entry对象中的key使用了WeakReference封装，也就是说Entry中的key是一个弱引用类型，而弱引用类型只能存活在下次GC之前。</p><p>如果一个线程调用ThreadLocal的set设置变量，当前ThreadLocalMap则会新增一条记录，但由于发生了一次垃圾回收，此时的key值就会被回收，而value值依然存在内存中，由于当前线程一直存在，所以value值将一直被引用。.</p><p>这些被垃圾回收掉的key就会一直存在一条引用链的关系：Thread --&gt; ThreadLocalMap–&gt;Entry–&gt;Value。这条引用链会导致Entry不会被回收，Value也不会被回收，但Entry中的key却已经被回收的情况发生，从而造成内存泄漏。</p><p>我们只需要在使用完该key值之后，将value值通过remove方法remove掉，就可以防止内存泄漏了。</p><p><img src="https://static001.geekbang.org/resource/image/8d/9a/8da35d95d5b31e3f0a582dbd4d47fd9a.jpeg?wh=1125*1252" alt=""></p><p>最后一个问题来自于WL同学。</p><p>内存泄漏是指不再使用的对象无法得到及时的回收，持续占用内存空间，从而造成内存空间的浪费。例如，我在<a href="https://time.geekbang.org/column/article/97215" target="_blank" rel="noopener">第03讲</a>中说到的，Java6中substring方法就可能会导致内存泄漏。</p><p>当调用substring方法时会调用new string构造函数，此时会复用原来字符串的char数组，而如果我们仅仅是用substring获取一小段字符，而在原本string字符串非常大的情况下，substring的对象如果一直被引用，由于substring里的char数组仍然指向原字符串，此时string字符串也无法回收，从而导致内存泄露。</p><p>内存溢出则是发生了OutOfMemoryException，内存溢出的情况有很多，例如堆内存空间不足，栈空间不足，还有方法区空间不足等都会导致内存溢出。</p><p>内存泄漏与内存溢出的关系：内存泄漏很容易导致内存溢出，但内存溢出不一定是内存泄漏导致的。</p><p>今天的答疑就到这里，<span class="orange">如果你还有其它问题，请在留言区中提出，</span>我会一一解答。最后欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他加入讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/b2/08/92f42622.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>尔冬橙</span>
  </div>
  <div class="_2_QraFYR_0">这是一门神课</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-02 01:45:32</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/f8/ca/1a1e190a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Nu11PointerEx</span>
  </div>
  <div class="_2_QraFYR_0">刘老师,我有个疑问，文中指出弱引用只能存活再下次GC之前，那假如线程在步骤A设置了threadlocal的值,然后需要在步骤B读出来，但是在AB之间发生了GC,这样会不会导致在步骤B中无法取到对应的值</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 如果线程没有销毁，也就是说该key值依然存在引用，即使是弱引用，也不会被回收掉。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-31 11:21:30</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/79/4b/740f91ca.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>-W.LI-</span>
  </div>
  <div class="_2_QraFYR_0">老师好!最近正好在看多线程编程指南。有个东西没搞明白。<br>我自己写了个demo把所有线程都在临界区调用wait方法，wait方法后是sleep方法。我在主线程调用了notifyall()，在临界区内打印了所有线程的状态，notifyall()之前都是waiting，之后都是blocked。出了临界区之后又打印了一次，发现有一个是timed_waiting，别的还是blocked。<br>从表现来看<br>notifyall():wait-&gt;blocked<br>调用notifyall()的线程出临界区释放锁锁:<br>竞争到锁定blocked-&gt;runnable，别的还是blocked。<br>之前老师说notifyall()在出临界区的时候调用比较好，可以防止被唤醒的阻塞状态线程，竞争不到锁再次阻塞。<br>notifyall是本地方法看不到实现。我想确认下<br>notifyall的逻辑是:唤醒waiting线程-&gt;尝试获取锁-&gt;获取不到blocked?<br>还是:所有waiting状态线程-&gt;blocked状态进去锁池队列。(只有在有线程释放锁的时候(出临界区)才会从锁池队列拿一个线程尝试获取锁)。我比较倾向于第二种。没看源码希望老师帮忙解惑下，我特意翻了之前的课在那边也留言了，老师在这回复就好了谢谢老师</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的，调用wait之后，会进入到WaitSet队列，当调用notify之后，默认策略是将其从WaitSet队列转至EntryList队列中，再尝试获取锁。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-20 10:30:18</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/cf/b5/d1ec6a7d.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Stalary</span>
  </div>
  <div class="_2_QraFYR_0">老师，ThreadLocal使用的时候我存储了一些请求相关的东西，没有使用remove，但是一次请求结束就会自动释放掉了吧，是不是不会出现内存泄漏？还是没太明白出现内存泄漏的场景，线程不都是工作完就会释放掉了吗</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的，如果我们的线程在使用ThreadLocal的set之后就立刻销毁了，此时之前set的线程的key值通过垃圾回收回收掉，此时value则会存在内存泄漏，而马上又有下一个线程使用ThreadLocal的set，则会清除之前key为null的value，这种情况下是不会出现内存泄漏的。<br><br>也就是ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。<br><br>我们可以使用set之后，sleep下该线程，等待其他请求都一起使用完了set，这样很容易重现内存中的一部分对象无法回收掉。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-28 10:44:33</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/1e/3a/5b21c01c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>nightmare</span>
  </div>
  <div class="_2_QraFYR_0">老师cms和g1能不能加餐讲详细一点 因为互联网公司 cms和g1问的非常多</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 好的，可以考虑。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-20 01:24:09</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/17/8b/4b/15ab499a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>风轻扬</span>
  </div>
  <div class="_2_QraFYR_0">老师，jdk1.6的substring导致内存泄漏的问题。大字符串截取完之后，我们直接把原大字符串的引用置为null，可以解决这个内存泄漏的问题吗？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 不行，只是原来的引用置为null了，但堆中的字符串对象依然不会被回收掉</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-11 07:18:14</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/fa/f7/91ac44c5.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Mq</span>
  </div>
  <div class="_2_QraFYR_0">老师threadlocal的entry不回收是因为value吗，另外我不理解jvm怎么知道我这次gc的时候key就可以回收，会不会出现我多次get的时候有一次就取不到了</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 如果线程还存活，此时get是能获取到的，因为还存在强引用。如果线程生命周期已经结束，则ThreadLocal的线程本地变量将会失去引用，我们知道ThreadLocal是成员变量，如果key值没有设置为弱引用，则结束生命周期的线程变量依然会存在ThreadLocal中。<br>所以，当线程生命周期结束，ThreadLocal的key又为了弱引用，key值就会在垃圾回收期被回收掉。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-22 19:59:15</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJOBwR7MCVqwZbPA5RQ2mjUjd571jUXUcBCE7lY5vSMibWn8D5S4PzDZMaAhRPdnRBqYbVOBTJibhJg/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>ヾ(◍°∇°◍)ﾉﾞ</span>
  </div>
  <div class="_2_QraFYR_0">java的垃圾回收使用的是复制算法和标记整理算法，这样对象的内存是变化的吧？那么引用它的栈上的地址也会变掉吗？如果是的话如果hashmap的key如果没有自己实现hashcode的话，是不是就会引起了内存泄漏和程序错乱</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的，对象的引用也被指向新的地址。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-22 10:06:02</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/17/27/ec30d30a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Jxin</span>
  </div>
  <div class="_2_QraFYR_0">抛砖引玉了，感谢老师的知无不尽。( ´◔‸◔`)</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-20 20:59:05</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/67/f4/9a1feb59.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>钱</span>
  </div>
  <div class="_2_QraFYR_0">我们的项目中ThreadLocal使用的蛮多的，使用原因是因为接口调用链长不想修改方法生命，但有些参数要透传就用ThreadLocal来透传参数。<br>老师能否介绍一下题ThreadLocal的最佳实践？什么场景下会使用？有什么坑需要填？怎么规避风险？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 我们可以读取大部分读写中间件实现源码，可以发现ThreadLocal使用的最为频繁，通常是通过ThreadLocal来获取当前线程的操作类型来实现读写数据源的切换。<br><br>在使用完之后实现remove操作，可以规避风险。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-12 07:03:49</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/17/8b/4b/15ab499a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>风轻扬</span>
  </div>
  <div class="_2_QraFYR_0">老师，jdk1.6的substring的内存泄漏问题。除了升级jdk版本，您有没有其他的办法。我在网上搜了一下，没有看到啥好办法</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 升级版本就好了，现在基本都是基于1.8版本了</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-11 09:40:01</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/4d/49/28e73b9c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>明翼</span>
  </div>
  <div class="_2_QraFYR_0">超哥，有问题请教下：<br>1）曾经被问到一个问题，就是java多线程分配内存的时候是如何控制并发冲突的那？<br>2）能不能结合代码把java内存创建的过程讲一次，比如成员变量的引用是在哪里分配的（我理解是堆上），堆上还是栈上，临时变量那，通过这种整体的讲解会对我们印象比较深刻。</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-22 09:26:12</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>ty_young</span>
  </div>
  <div class="_2_QraFYR_0">老师您好，card table只是老年代才维护的吧，那G1垃圾收集器的RSet也是只维护老年代的引用么(老年代引用年轻代和老年代引用老年代)</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-05-02 14:17:37</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>ty_young</span>
  </div>
  <div class="_2_QraFYR_0">求老师把cms，g1讲得详细点，求加餐</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-04-29 22:16:44</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/dc/fc/5c3ad841.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>asura</span>
  </div>
  <div class="_2_QraFYR_0">每次看完课程，课后评论也会看完。大家看问题的角度不同，思考纬度也不同，着实学到了很多 👍。感谢老师的热情回答！</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-01-24 18:57:28</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>ty_young</span>
  </div>
  <div class="_2_QraFYR_0">真的受益颇多，谢谢老师</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-10-27 21:18:05</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/26%20_%20%E7%AD%94%E7%96%91%E8%AF%BE%E5%A0%82%EF%BC%9A%E6%A8%A1%E5%9D%97%E5%9B%9B%E7%83%AD%E7%82%B9%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/25 _ 内存持续上升，我该如何排查问题？" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/25%20_%20%E5%86%85%E5%AD%98%E6%8C%81%E7%BB%AD%E4%B8%8A%E5%8D%87%EF%BC%8C%E6%88%91%E8%AF%A5%E5%A6%82%E4%BD%95%E6%8E%92%E6%9F%A5%E9%97%AE%E9%A2%98%EF%BC%9F/">极客时间/Java性能调优实战/25 _ 内存持续上升，我该如何排查问题？</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/25%20_%20%E5%86%85%E5%AD%98%E6%8C%81%E7%BB%AD%E4%B8%8A%E5%8D%87%EF%BC%8C%E6%88%91%E8%AF%A5%E5%A6%82%E4%BD%95%E6%8E%92%E6%9F%A5%E9%97%AE%E9%A2%98%EF%BC%9F/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:42.898Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="25 _ 内存持续上升，我该如何排查问题？" src="https://static001.geekbang.org/resource/audio/30/fc/300f6947de14c0197145f2122c8ea7fc.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>我想你肯定遇到过内存溢出，或是内存使用率过高的问题。碰到内存持续上升的情况，其实我们很难从业务日志中查看到具体的问题，<span class="orange">那么面对多个进程以及大量业务线程，我们该如何精准地找到背后的原因呢？</span></p><h2>常用的监控和诊断内存工具</h2><p>工欲善其事，必先利其器。平时排查内存性能瓶颈时，我们往往需要用到一些Linux命令行或者JDK工具来辅助我们监测系统或者虚拟机内存的使用情况，下面我就来介绍几种好用且常用的工具。</p><h3>Linux命令行工具之top命令</h3><p>top命令是我们在Linux下最常用的命令之一，它可以实时显示正在执行进程的CPU使用率、内存使用率以及系统负载等信息。其中上半部分显示的是系统的统计信息，下半部分显示的是进程的使用率统计信息。</p><p><img src="https://static001.geekbang.org/resource/image/36/49/3633095ed54d1ef22fc08310497d6b49.jpg?wh=1778*660" alt=""></p><p>除了简单的top之外，我们还可以通过top -Hp pid查看具体线程使用系统资源情况：</p><p><img src="https://static001.geekbang.org/resource/image/1e/47/1e4429a9785ae4e6c0884655ee8b5747.jpg?wh=1630*780" alt=""></p><h3>Linux命令行工具之vmstat命令</h3><p>vmstat是一款指定采样周期和次数的功能性监测工具，我们可以看到，它不仅可以统计内存的使用情况，还可以观测到CPU的使用率、swap的使用情况。但vmstat一般很少用来查看内存的使用情况，而是经常被用来观察进程的上下文切换。</p><p><img src="https://static001.geekbang.org/resource/image/31/62/31a79622cdcadda4e9003b075378dc62.jpg?wh=1488*178" alt=""></p><ul>
<li>r：等待运行的进程数；</li>
<li>b：处于非中断睡眠状态的进程数；</li>
<li>swpd：虚拟内存使用情况；</li>
<li>free：空闲的内存；</li>
<li>buff：用来作为缓冲的内存数；</li>
<li>si：从磁盘交换到内存的交换页数量；</li>
<li>so：从内存交换到磁盘的交换页数量；</li>
<li>bi：发送到块设备的块数；</li>
<li>bo：从块设备接收到的块数；</li>
<li>in：每秒中断数；</li>
<li>cs：每秒上下文切换次数；</li>
<li>us：用户CPU使用时间；</li>
<li>sy：内核CPU系统使用时间；</li>
<li>id：空闲时间；</li>
<li>wa：等待I/O时间；</li>
<li>st：运行虚拟机窃取的时间。</li>
</ul><!-- [[[read_end]]] --><h3>Linux命令行工具之pidstat命令</h3><p>pidstat是Sysstat中的一个组件，也是一款功能强大的性能监测工具，我们可以通过命令：yum install sysstat安装该监控组件。之前的top和vmstat两个命令都是监测进程的内存、CPU以及I/O使用情况，而pidstat命令则是深入到线程级别。</p><p>通过pidstat -help命令，我们可以查看到有以下几个常用的参数来监测线程的性能：</p><p><img src="https://static001.geekbang.org/resource/image/90/46/90d26ef49ad94510062ac3f36727a346.jpg?wh=1440*178" alt=""></p><p>常用参数：</p><ul>
<li>-u：默认的参数，显示各个进程的cpu使用情况；</li>
<li>-r：显示各个进程的内存使用情况；</li>
<li>-d：显示各个进程的I/O使用情况；</li>
<li>-w：显示每个进程的上下文切换情况；</li>
<li>-p：指定进程号；</li>
<li>-t：显示进程中线程的统计信息。</li>
</ul><p>我们可以通过相关命令（例如ps或jps）查询到相关进程ID，再运行以下命令来监测该进程的内存使用情况：</p><p><img src="https://static001.geekbang.org/resource/image/18/61/184df3ee5ab0a920f506b3daa6250a61.jpg?wh=1562*460" alt=""></p><p>其中pidstat的参数-p用于指定进程ID，-r表示监控内存的使用情况，1表示每秒的意思，3则表示采样次数。</p><p>其中显示的几个关键指标的含义是：</p><ul>
<li>Minflt/s：任务每秒发生的次要错误，不需要从磁盘中加载页；</li>
<li>Majflt/s：任务每秒发生的主要错误，需要从磁盘中加载页；</li>
<li>VSZ：虚拟地址大小，虚拟内存使用KB；</li>
<li>RSS：常驻集合大小，非交换区内存使用KB。</li>
</ul><p>如果我们需要继续查看该进程下的线程内存使用率，则在后面添加-t指令即可：</p><p><img src="https://static001.geekbang.org/resource/image/3c/72/3c9072c659a91b5f83cbc1a112ddcc72.jpg?wh=1574*694" alt=""></p><p>我们知道，Java是基于JVM上运行的，大部分内存都是在JVM的用户内存中创建的，所以除了通过以上Linux命令来监控整个服务器内存的使用情况之外，我们更需要知道JVM中的内存使用情况。JDK中就自带了很多命令工具可以监测到JVM的内存分配以及使用情况。</p><h3>JDK工具之jstat命令</h3><p>jstat可以监测Java应用程序的实时运行情况，包括堆内存信息以及垃圾回收信息。我们可以运行jstat -help查看一些关键参数信息：</p><p><img src="https://static001.geekbang.org/resource/image/42/e8/42880a93eb63ae6854a7920e73a751e8.jpg?wh=1550*610" alt=""></p><p>再通过jstat -option查看jstat有哪些操作：</p><p><img src="https://static001.geekbang.org/resource/image/7a/7d/7af697d9cfd6002a49063ab2464d5f7d.jpg?wh=1506*386" alt=""></p><ul>
<li>-class：显示ClassLoad的相关信息；</li>
<li>-compiler：显示JIT编译的相关信息；</li>
<li>-gc：显示和gc相关的堆信息；</li>
<li>-gccapacity：显示各个代的容量以及使用情况；</li>
<li>-gcmetacapacity：显示Metaspace的大小；</li>
<li>-gcnew：显示新生代信息；</li>
<li>-gcnewcapacity：显示新生代大小和使用情况；</li>
<li>-gcold：显示老年代和永久代的信息；</li>
<li>-gcoldcapacity ：显示老年代的大小；</li>
<li>-gcutil：显示垃圾收集信息；</li>
<li>-gccause：显示垃圾回收的相关信息（通-gcutil），同时显示最后一次或当前正在发生的垃圾回收的诱因；</li>
<li>-printcompilation：输出JIT编译的方法信息。</li>
</ul><p>它的功能比较多，在这里我例举一个常用功能，如何使用jstat查看堆内存的使用情况。我们可以用jstat -gc pid查看：</p><p><img src="https://static001.geekbang.org/resource/image/e5/68/e59188982cf5b75243a8c333bfead068.jpg?wh=2464*128" alt=""></p><ul>
<li>S0C：年轻代中To Survivor的容量（单位KB）；</li>
<li>S1C：年轻代中From Survivor的容量（单位KB）；</li>
<li>S0U：年轻代中To Survivor目前已使用空间（单位KB）；</li>
<li>S1U：年轻代中From Survivor目前已使用空间（单位KB）；</li>
<li>EC：年轻代中Eden的容量（单位KB）；</li>
<li>EU：年轻代中Eden目前已使用空间（单位KB）；</li>
<li>OC：Old代的容量（单位KB）；</li>
<li>OU：Old代目前已使用空间（单位KB）；</li>
<li>MC：Metaspace的容量（单位KB）；</li>
<li>MU：Metaspace目前已使用空间（单位KB）；</li>
<li>YGC：从应用程序启动到采样时年轻代中gc次数；</li>
<li>YGCT：从应用程序启动到采样时年轻代中gc所用时间(s)；</li>
<li>FGC：从应用程序启动到采样时old代（全gc）gc次数；</li>
<li>FGCT：从应用程序启动到采样时old代（全gc）gc所用时间(s)；</li>
<li>GCT：从应用程序启动到采样时gc用的总时间(s)。</li>
</ul><h3>JDK工具之jstack命令</h3><p>这个工具在模块三的<a href="https://time.geekbang.org/column/article/105234" target="_blank" rel="noopener">答疑课堂</a>中介绍过，它是一种线程堆栈分析工具，最常用的功能就是使用 jstack pid 命令查看线程的堆栈信息，通常会结合top -Hp pid 或 pidstat -p pid -t一起查看具体线程的状态，也经常用来排查一些死锁的异常。</p><p><img src="https://static001.geekbang.org/resource/image/28/88/2869503e8d5460e36b3fd3e1a52a8888.jpg?wh=2328*414" alt=""></p><p>每个线程堆栈的信息中，都可以查看到线程ID、线程的状态（wait、sleep、running 等状态）以及是否持有锁等。</p><h3>JDK工具之jmap命令</h3><p>在<a href="https://time.geekbang.org/column/article/108139" target="_blank" rel="noopener">第23讲</a>中我们使用过jmap查看堆内存初始化配置信息以及堆内存的使用情况。那么除了这个功能，我们其实还可以使用jmap输出堆内存中的对象信息，包括产生了哪些对象，对象数量多少等。</p><p>我们可以用jmap来查看堆内存初始化配置信息以及堆内存的使用情况：</p><p><img src="https://static001.geekbang.org/resource/image/80/3f/808870b42f5f6525d79f70fd287a293f.jpg?wh=1206*1304" alt=""></p><p>我们可以使用jmap -histo[:live] pid查看堆内存中的对象数目、大小统计直方图，如果带上live则只统计活对象：</p><p><img src="https://static001.geekbang.org/resource/image/74/7b/74f42fa2b48ceaff869472f6061c1c7b.jpg?wh=1516*1028" alt=""></p><p>我们可以通过jmap命令把堆内存的使用情况dump到文件中：</p><p><img src="https://static001.geekbang.org/resource/image/f3/17/f3c17fd9bb436599fb48cf151ee7ba17.jpg?wh=1310*144" alt=""></p><p>我们可以将文件下载下来，使用 <a href="http://www.eclipse.org/mat/" target="_blank" rel="noopener">MAT</a> 工具打开文件进行分析：</p><p><img src="https://static001.geekbang.org/resource/image/3c/43/3cc14844625cebcc1cdb836e5ccbfc43.jpg?wh=1492*1384" alt=""></p><p>下面我们用一个实战案例来综合使用下刚刚介绍的几种工具，具体操作一下如何分析一个内存泄漏问题。</p><h2>实战演练</h2><p>我们平时遇到的内存溢出问题一般分为两种，一种是由于大峰值下没有限流，瞬间创建大量对象而导致的内存溢出；另一种则是由于内存泄漏而导致的内存溢出。</p><p>使用限流，我们一般就可以解决第一种内存溢出问题，但其实很多时候，内存溢出往往是内存泄漏导致的，这种问题就是程序的BUG，我们需要及时找到问题代码。</p><p><strong>下面我模拟了一个内存泄漏导致的内存溢出案例，我们来实践一下。</strong></p><p>我们知道，ThreadLocal的作用是提供线程的私有变量，这种变量可以在一个线程的整个生命周期中传递，可以减少一个线程在多个函数或类中创建公共变量来传递信息，避免了复杂度。但在使用时，如果ThreadLocal使用不恰当，就可能导致内存泄漏。</p><p>这个案例的场景就是ThreadLocal，下面我们模拟对每个线程设置一个本地变量。运行以下代码，系统一会儿就发送了内存溢出异常：</p><pre><code>    @RequestMapping(value = &quot;/test0&quot;)
    public String test0(HttpServletRequest request) {
        ThreadLocal&lt;Byte[]&gt; localVariable = new ThreadLocal&lt;Byte[]&gt;();
        localVariable.set(new Byte[4096*1024]);// 为线程添加变量
        return &quot;success&quot;;
    }
</code></pre><p>在启动应用程序之前，我们可以通过HeapDumpOnOutOfMemoryError和HeapDumpPath这两个参数开启堆内存异常日志，通过以下命令启动应用程序：</p><pre><code>java -jar -Xms1000m -Xmx4000m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof  -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/tmp/heapTest.log heapTest-0.0.1-SNAPSHOT.jar
</code></pre><p>首先，请求test0链接10000次，这个时候我们请求test0的接口报异常了。</p><p><img src="https://static001.geekbang.org/resource/image/60/dc/60ab8d7847a55a9bcf84d17ecd11ebdc.jpg?wh=2350*574" alt=""></p><p>通过日志，我们很好分辨这是一个内存溢出异常。我们首先通过Linux系统命令查看进程在整个系统中内存的使用率是多少，最简单就是top命令了。</p><p><img src="https://static001.geekbang.org/resource/image/d2/37/d2ad570e1fff2a64a1924c2852f93e37.jpg?wh=1494*886" alt=""></p><p>从top命令查看进程的内存使用情况，可以发现在机器只有8G内存且只分配了4G内存给Java进程的情况下，Java进程内存使用率已经达到了55%，再通过top -Hp pid查看具体线程占用系统资源情况。</p><p><img src="https://static001.geekbang.org/resource/image/6f/a7/6fdea40b5ff4f2f0744e019c3bef79a7.jpg?wh=1614*924" alt=""></p><p>再通过jstack pid查看具体线程的堆栈信息，可以发现该线程一直处于 TIMED_WAITING 状态，此时CPU使用率和负载并没有出现异常，我们可以排除死锁或I/O阻塞的异常问题了。</p><p><img src="https://static001.geekbang.org/resource/image/4b/87/4bfb58d626f988260e016a2bdf0e8687.jpg?wh=2330*386" alt=""></p><p>我们再通过jmap查看堆内存的使用情况，可以发现，老年代的使用率几乎快占满了，而且内存一直得不到释放：</p><p><img src="https://static001.geekbang.org/resource/image/fe/71/feb358259ea8b3ed2b67e868c101d271.jpg?wh=1350*1304" alt=""></p><p>通过以上堆内存的情况，我们基本可以判断系统发生了内存泄漏。下面我们就需要找到具体是什么对象一直无法回收，什么原因导致了内存泄漏。</p><p>我们需要查看具体的堆内存对象，看看是哪个对象占用了堆内存，可以通过jmap查看存活对象的数量：</p><p><img src="https://static001.geekbang.org/resource/image/c5/d9/c5b89deb306a2c470e606fa9c49dd0d9.jpg?wh=1634*776" alt=""></p><p>Byte对象占用内存明显异常，说明代码中Byte对象存在内存泄漏，我们在启动时，已经设置了dump文件，通过MAT打开dump的内存日志文件，我们可以发现MAT已经提示了byte内存异常：</p><p><img src="https://static001.geekbang.org/resource/image/4c/63/4ceb91714afa77b54d1112a0e1f0c863.jpg?wh=1492*1376" alt=""></p><p>再点击进入到Histogram页面，可以查看到对象数量排序，我们可以看到Byte[]数组排在了第一位，选中对象后右击选择with incomming reference功能，可以查看到具体哪个对象引用了这个对象。</p><p><img src="https://static001.geekbang.org/resource/image/5a/91/5a651a2f52dfed72712543f7680de091.jpg?wh=1486*1380" alt=""></p><p>在这里我们就可以很明显地查看到是ThreadLocal这块的代码出现了问题。</p><p><img src="https://static001.geekbang.org/resource/image/2b/a2/2bed3871097249d64ccf4c79d68109a2.jpg?wh=1498*1380" alt=""></p><h2>总结</h2><p>在一些比较简单的业务场景下，排查系统性能问题相对来说简单，且容易找到具体原因。但在一些复杂的业务场景下，或是一些开源框架下的源码问题，相对来说就很难排查了，<span class="orange">有时候通过工具只能猜测到可能是某些地方出现了问题，而实际排查则要结合源码做具体分析。</span></p><p>可以说没有捷径，排查线上的性能问题本身就不是一件很简单的事情，除了将今天介绍的这些工具融会贯通，还需要我们不断地去累积经验，真正做到性能调优。</p><h2>思考题</h2><p>除了以上我讲到的那些排查内存性能瓶颈的工具之外，你知道要在代码中对JVM的内存进行监控，常用的方法是什么？</p><p>期待在留言区看到你的分享。也欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他一起讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/54/9a/76c0af70.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>每天晒白牙</span>
  </div>
  <div class="_2_QraFYR_0">放两篇自己在工作中排查JVM问题的两篇文章【非广告，纯技术文】<br>https:&#47;&#47;mp.weixin.qq.com&#47;s&#47;ji_8NhN4NnEHrfAlA9X_ag<br><br>https:&#47;&#47;mp.weixin.qq.com&#47;s&#47;IPi3xiordGh-zcSSRie6nA</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 赞！</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-18 18:06:28</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/14/d0/42/6fd01fb9.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>我已经设置了昵称</span>
  </div>
  <div class="_2_QraFYR_0">老师是否可以讲下如何避免threadLocal内存泄漏呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 我们知道，ThreadLocal是基于ThreadLocalMap实现的，这个Map的Entry继承了WeakReference，而Entry对象中的key使用了WeakReference封装，也就是说Entry中的key是一个弱引用类型，而弱引用类型只能存活在下次GC之前。<br><br>如果一个线程调用ThreadLocal的set设置变量，当前ThreadLocalMap则新增一条记录，此时ThreadLocal实例没有外部强引用，当发生一次垃圾回收，此时key值被回收，而value值依然存在内存中，由于当前线程一直存在，所以value值将一直被引用。.<br><br>这些被垃圾回收掉的key就存在一条引用链的关系一直存在：Thread --&gt; ThreadLocalMap--&gt;Entry--&gt;Value，这条引用链会导致Entry不会回收，Value也不会回收，但Entry中的Key却已经被回收的情况，造成内存泄漏。<br><br>我们只需要在使用完该key值之后，通过remove方法remove掉，就可以防止内存泄漏了。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-18 23:39:38</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/e9/0b/1171ac71.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>WL</span>
  </div>
  <div class="_2_QraFYR_0">请问一下老师内存泄露和内存溢出具体有啥区别，有点不太理解内存泄露的概念。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 内存泄漏是指不再使用的对象无法得到及时的回收，持续占用内存空间，从而造成内存空间的浪费。例如，我们之前在第3讲中聊到的在Java6中substring方法可能会导致内存泄漏情况发生。当调用substring方法时会调用new string构造函数，此时会复用原来字符串的char数组，而如果我们仅仅是用substring获取一小段字符，而原本string字符串非常大的情况下，substring的对象如果一直被引用，由于substring的里面的char数组仍然指向原字符串，此时string字符串也无法回收，从而导致内存泄露。<br><br>内存溢出则是发生了OutOfMemoryException，内存溢出的情况有很多，例如堆内存空间不足，栈空间不足，以及方法区空间不足都会发生内存溢出异常。<br><br>内存泄漏与内存溢出的关系：内存泄漏很容易导致内存溢出，但内存溢出不一定是内存泄漏导致的。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-18 20:59:11</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/fe/01/3f3ae95e.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>怪盗キッド</span>
  </div>
  <div class="_2_QraFYR_0">我开源了一个 Java 性能监控工具，就是用 JDK 自带的接口实现的。<br>GitHub 地址：https:&#47;&#47;github.com&#47;LinShunKang&#47;MyPerf4J</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 👍</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-22 00:41:17</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/91/50/6aceefec.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Rain</span>
  </div>
  <div class="_2_QraFYR_0">老师，为什么线程要sleep一下，看了注释还是不理解，求告知</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 正常情况下，如果一个线程set之后，该线程销毁了，然后key值由于弱引用刚好遇到一次GC，被回收了，此时value已经出现内存泄漏。而threadlocal为了解决这个问题，在后面的线程进行set时，会把之前key值为null的value清空掉，所以就不会出现大量内存泄漏了。<br><br>所以我们要模拟的就是，在后面进来的线程set之前，保证之前的线程还没有销毁，之前的key value就会保持，这样我们能模拟出大量value内存泄漏的情况出现。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-04 16:59:23</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/2c/87/9a3d80dc.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>昨夜的柠檬</span>
  </div>
  <div class="_2_QraFYR_0">实际项目中很多都是这样的，老师正确的写法应该是怎样的？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 正确的写法是在set之后，记得在finally里面remove掉。<br><br>try{<br>  localthread.set(&quot;test&quot;);<br>}finally{<br>   localthread.remove(&quot;test&quot;);<br>}</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-10-27 14:38:27</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eqWicx6gYmNqGYC8V9xo31w9hjsJrZB1fogG6ibBc0KObo2q3n1xndl6BcriahzHKnGvWy3sgCxVlH1w/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>CRann</span>
  </div>
  <div class="_2_QraFYR_0">老师，刚看案例top命令后java的pid是1444，可是为什么后来查线程信息变成top -Hd 1593了？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 截图截错了，自己操作的时候记得输入正确的pid就好了。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-31 10:19:44</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/9b/7a/58327b6d.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>殿小二</span>
  </div>
  <div class="_2_QraFYR_0">老师 &quot;而threadlocal为了解决这个问题，在后面的线程进行set时，会把之前key值为null的value清空掉，所以就不会出现大量内存泄漏了。&quot;  后面的线程set的时候也只会在自己持有的ThreadLocalMap上进行操作吧,没有所谓的清空 key为null的value的值吧</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的，后面线程的set只是在当前线程的ThreadLocalMap上进行操作，不能清空其他线程ThreadLocalMap上已经泄漏的value值。这里指的是同一个线程，ThreadLocal实例没有外部强引用的情况下被回收了，此时key值会被回收，下一次在相同线程下set，value值会被清掉。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-12-03 17:05:40</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/d0/a1/07027529.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>偏偏喜欢你</span>
  </div>
  <div class="_2_QraFYR_0">老是您好最近看到项目有报内存溢出，发现是byte[]的问题，但是在Histogram 下看到排在第一位的是char[]数组，排第二的是byte[]<br>我是去排查char[]呢还是byte[]</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 这两个都是基础数据类型数组，例如char[]是String的基础数据类型，byte[]则是数据传输字节流的基础数据类型，排在第一二是比较常见的，我们需要再看看大小，如果异常大，那就是该基础数据类型之上的某个引用类型的问题。可以通过工具再展开树看看封装基础数据类型的引用类型是什么。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-11-21 17:24:45</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/e3/f1/346bd356.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Bruce</span>
  </div>
  <div class="_2_QraFYR_0">问下老师，jmap和jstack命令能查历史的数据，譬如想查昨天的？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 只能查看运行时的数据，如果需要历史数据，可以在JVM启动参数中加入dump日志参数，启动长时间JVM日志监控：<br>启动OOM监控日志：-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=&#47;tmp&#47;heapdump.hprof <br>启动GC日志：-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:&#47;tmp&#47;heapTest.log</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-05-13 13:29:25</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/16/5e/86/40877404.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>星星滴蓝天</span>
  </div>
  <div class="_2_QraFYR_0">代码中对jvm监控常用方法是啥？我翻了翻留言，没有人问这个问题的</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 可以通过ManagementFactory中的RuntimeMXBean实时获取JVM对应的值</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-05 17:52:02</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/d0/dd/a4df05d4.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Alex</span>
  </div>
  <div class="_2_QraFYR_0">老师好，不好意思，想问一下，本门课程案例代码的git地址在哪里？我没有找到</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 由于代码比较少，这篇没有提交到github上，麻烦自己建个项目，拷贝下文章中的代码上去就好了。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-01-29 01:38:39</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/49/3c/5d54c510.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>skull</span>
  </div>
  <div class="_2_QraFYR_0">treadLocal会随着线程被回收而消失的，不会一直存在，极端情况才会内存泄漏</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-12-10 19:14:39</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83eqk1XbuxMP8OPv1QUEOg1IR2bCWaBcr19Z8KiaSg2fnHs7PicmJRtD7ibdEK4As692g3xtTdajjWWdXw/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>vvip</span>
  </div>
  <div class="_2_QraFYR_0">老师，请问JVM上始终开启HeapDumpOnOutOfMemoryError这个参数，会影响性能吗？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 有性能损耗</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-03-30 23:54:56</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/6f/c9/22022094.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>丁奇老师的粉丝</span>
  </div>
  <div class="_2_QraFYR_0">老师您好，看了您的课程收货颇丰！谢谢<br>现在有个问题想咨询下<br><br>前提：jdk7u24  xms8g xmx8g g1垃圾回收<br>现象：<br>堆内存使用量从2G一直到6.3G都没有young gc  和 full gc<br><br>当堆内存使用量到了7G的时候直接进行了full gc<br>并且周期性重复上面的full gc<br>查看GC日志  eden区回收前高达6.3G<br><br>请问老师。现在该如何调优呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 如果没有设置年轻代与老年代的比例，默认分配给年轻代最大比例为60%，而且默认会先触发young gc，所以你说的这种情况比较少见，检查是否长时间存活的对象太多导致的。<br><br>这种情况优化设置参数已经没有很明显的作用了，建议先查找内存爆满的原因。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-11-09 10:58:37</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/18/b3/15/30822e33.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>小麦</span>
  </div>
  <div class="_2_QraFYR_0">Linux性能工具的这部分内容和《实战 JAVA 虚拟机》的 6.1 节完全一样</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-11-19 14:50:54</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/PiajxSqBRaELZPnUAiajaR5C25EDLWeJURggyiaOP5GGPe2qlwpQcm5e3ybib8OsP4tvddFDLVRSNNGL5I3SFPJHsA/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>null</span>
  </div>
  <div class="_2_QraFYR_0">老师，请问一下，文章是二次修改过么？评论区有些关键字如：sleep，test0，test1。<br>我在文章只看到 test0 方法。另两个都没找到。<br>谢谢！</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-04-29 17:02:41</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/7d/a5/930c9103.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Feng</span>
  </div>
  <div class="_2_QraFYR_0">没看到有test1啊。。。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 之前的代码已经优化了，所以去掉了test1，重写写了test0方法，两个方法对于大家来说不是很好理解</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-31 10:30:36</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/d7/df/fc0a6709.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>WolvesLeader</span>
  </div>
  <div class="_2_QraFYR_0">java -jar -Xms1000m -Xmx4000m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=&#47;tmp&#47;heapdump.hprof -Xms1g -Xmx1g -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:&#47;tmp&#47;heapTest.log heapTest-0.0.1-SNAPSHOT.jar<br>配置了2个-Xms和-Xmx，为啥要配置2个</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 一个就够了，已修正</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-21 14:05:48</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/14/5f/73/bb3dc468.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>拒绝</span>
  </div>
  <div class="_2_QraFYR_0">我用ab测试，设置请求数量一万，请求test0，内存就溢出，；还没请求到test1,？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 内存泄露导致有大量对象无法回收，占满了堆内存情况下，就会导致内存溢出。我在这里加了一个test1只是为了创建更多的对象，从而更容易发生内存溢出。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-18 14:04:07</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/25%20_%20%E5%86%85%E5%AD%98%E6%8C%81%E7%BB%AD%E4%B8%8A%E5%8D%87%EF%BC%8C%E6%88%91%E8%AF%A5%E5%A6%82%E4%BD%95%E6%8E%92%E6%9F%A5%E9%97%AE%E9%A2%98%EF%BC%9F/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/24 _ 如何优化JVM内存分配？" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/24%20_%20%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96JVM%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%EF%BC%9F/">极客时间/Java性能调优实战/24 _ 如何优化JVM内存分配？</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/24%20_%20%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96JVM%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%EF%BC%9F/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:37.060Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="24 _ 如何优化JVM内存分配？" src="https://static001.geekbang.org/resource/audio/6d/c7/6dec37d734d5d11fb373b069946de5c7.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>JVM调优是一个系统而又复杂的过程，但我们知道，在大多数情况下，我们基本不用去调整JVM内存分配，因为一些初始化的参数已经可以保证应用服务正常稳定地工作了。</p><p>但所有的调优都是有目标性的，JVM内存分配调优也一样。没有性能问题的时候，我们自然不会随意改变JVM内存分配的参数。那有了问题呢？<span class="orange">有了什么样的性能问题我们需要对其进行调优呢？又该如何调优呢？</span>这就是我今天要分享的内容。</p><h2>JVM内存分配性能问题</h2><p>谈到JVM内存表现出的性能问题时，你可能会想到一些线上的JVM内存溢出事故。但这方面的事故往往是应用程序创建对象导致的内存回收对象难，一般属于代码编程问题。</p><p>但其实很多时候，在应用服务的特定场景下，JVM内存分配不合理带来的性能表现并不会像内存溢出问题这么突出。可以说如果你没有深入到各项性能指标中去，是很难发现其中隐藏的性能损耗。</p><p>JVM内存分配不合理最直接的表现就是频繁的GC，这会导致上下文切换等性能问题，从而降低系统的吞吐量、增加系统的响应时间。因此，<span class="orange">如果你在线上环境或性能测试时，发现频繁的GC，且是正常的对象创建和回收，这个时候就需要考虑调整JVM内存分配了，</span>从而减少GC所带来的性能开销。</p><!-- [[[read_end]]] --><h2>对象在堆中的生存周期</h2><p>了解了性能问题，那需要做的势必就是调优了。但先别急，在了解JVM内存分配的调优过程之前，我们先来看看一个新创建的对象在堆内存中的生存周期，为后面的学习打下基础。</p><p>在<a href="https://time.geekbang.org/column/article/106203" target="_blank" rel="noopener">第20讲</a>中，我讲过JVM内存模型。我们知道，在JVM内存模型的堆中，堆被划分为新生代和老年代，新生代又被进一步划分为Eden区和Survivor区，最后Survivor由From Survivor和To Survivor组成。</p><p>当我们新建一个对象时，对象会被优先分配到新生代的Eden区中，这时虚拟机会给对象定义一个对象年龄计数器（通过参数-XX:MaxTenuringThreshold设置）。</p><p>同时，也有另外一种情况，当Eden空间不足时，虚拟机将会执行一个新生代的垃圾回收（Minor GC）。这时JVM会把存活的对象转移到Survivor中，并给对象的年龄+1。对象在Survivor中同样也会经历MinorGC，每经过一次MinorGC，对象的年龄将会+1。</p><p>当然了，内存空间也是有设置阈值的，可以通过参数-XX:PetenureSizeThreshold设置直接被分配到老年代的最大对象，这时如果分配的对象超过了设置的阀值，对象就会直接被分配到老年代，这样做的好处就是可以减少新生代的垃圾回收。</p><h2>查看JVM堆内存分配</h2><p>我们知道了一个对象从创建至回收到堆中的过程，接下来我们再来了解下JVM堆内存是如何分配的。在默认不配置JVM堆内存大小的情况下，JVM根据默认值来配置当前内存大小。我们可以通过以下命令来查看堆内存配置的默认值：</p><pre><code>java -XX:+PrintFlagsFinal -version | grep HeapSize 
jmap -heap 17284
</code></pre><p><img src="https://static001.geekbang.org/resource/image/43/d5/436338cc5251291eeb6dbb57467443d5.png?wh=803*118" alt=""></p><p><img src="https://static001.geekbang.org/resource/image/59/de/59941c65600fe4a11e5bc6b8304fe0de.png?wh=704*675" alt=""></p><p>通过命令，我们可以获得在这台机器上启动的JVM默认最大堆内存为1953MB，初始化大小为124MB。</p><p>在JDK1.7中，默认情况下年轻代和老年代的比例是1:2，我们可以通过–XX:NewRatio重置该配置项。年轻代中的Eden和To Survivor、From Survivor的比例是8:1:1，我们可以通过-XX:SurvivorRatio重置该配置项。</p><p>在JDK1.7中如果开启了-XX:+UseAdaptiveSizePolicy配置项，JVM将会动态调整Java堆中各个区域的大小以及进入老年代的年龄，–XX:NewRatio和-XX:SurvivorRatio将会失效，而JDK1.8是默认开启-XX:+UseAdaptiveSizePolicy配置项的。</p><p>还有，在JDK1.8中，不要随便关闭UseAdaptiveSizePolicy配置项，除非你已经对初始化堆内存/最大堆内存、年轻代/老年代以及Eden区/Survivor区有非常明确的规划了。否则JVM将会分配最小堆内存，年轻代和老年代按照默认比例1:2进行分配，年轻代中的Eden和Survivor则按照默认比例8:2进行分配。这个内存分配未必是应用服务的最佳配置，因此可能会给应用服务带来严重的性能问题。</p><h2>JVM内存分配的调优过程</h2><p>我们先使用JVM的默认配置，观察应用服务的运行情况，下面我将结合一个实际案例来讲述。现模拟一个抢购接口，假设需要满足一个5W的并发请求，且每次请求会产生20KB对象，我们可以通过千级并发创建一个1MB对象的接口来模拟万级并发请求产生大量对象的场景，具体代码如下：</p><pre><code>	
	@RequestMapping(value = &quot;/test1&quot;)
	public String test1(HttpServletRequest request) {
		List&lt;Byte[]&gt; temp = new ArrayList&lt;Byte[]&gt;();
		
		Byte[] b = new Byte[1024*1024];
		temp.add(b);
		
		return &quot;success&quot;;
	}
</code></pre><h3>AB压测</h3><p>分别对应用服务进行压力测试，以下是请求接口的吞吐量和响应时间在不同并发用户数下的变化情况：</p><p><img src="https://static001.geekbang.org/resource/image/8b/26/8b67579af661a666dff89d16ab2e2f26.jpg?wh=980*607" alt=""></p><p>可以看到，当并发数量到了一定值时，吞吐量就上不去了，响应时间也迅速增加。那么，在JVM内部运行又是怎样的呢？</p><h3>分析GC日志</h3><p>此时我们可以通过GC日志查看具体的回收日志。我们可以通过设置VM配置参数，将运行期间的GC日志 dump下来，具体配置参数如下：</p><pre><code> -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/log/heapTest.log
</code></pre><p>以下是各个配置项的说明：</p><ul>
<li>-XX:PrintGCTimeStamps：打印GC具体时间；</li>
<li>-XX:PrintGCDetails ：打印出GC详细日志；</li>
<li>-Xloggc: path：GC日志生成路径。</li>
</ul><p>收集到GC日志后，我们就可以使用<a href="https://time.geekbang.org/column/article/107396" target="_blank" rel="noopener">第22讲</a>中介绍过的GCViewer工具打开它，进而查看到具体的GC日志如下：</p><p><img src="https://static001.geekbang.org/resource/image/bf/d1/bffd496963f6bbd345092c1454524dd1.jpeg?wh=1112*705" alt=""></p><p>主页面显示FullGC发生了13次，右下角显示年轻代和老年代的内存使用率几乎达到了100%。而FullGC会导致stop-the-world的发生，从而严重影响到应用服务的性能。此时，我们需要调整堆内存的大小来减少FullGC的发生。</p><h3>参考指标</h3><p>我们可以将某些指标的预期值作为参考指标，上面的GC频率就是其中之一，那么还有哪些指标可以为我们提供一些具体的调优方向呢？</p><p><strong>GC频率：</strong>高频的FullGC会给系统带来非常大的性能消耗，虽然MinorGC相对FullGC来说好了许多，但过多的MinorGC仍会给系统带来压力。</p><p><strong>内存：</strong>这里的内存指的是堆内存大小，堆内存又分为年轻代内存和老年代内存。首先我们要分析堆内存大小是否合适，其实是分析年轻代和老年代的比例是否合适。如果内存不足或分配不均匀，会增加FullGC，严重的将导致CPU持续爆满，影响系统性能。</p><p><strong>吞吐量：</strong>频繁的FullGC将会引起线程的上下文切换，增加系统的性能开销，从而影响每次处理的线程请求，最终导致系统的吞吐量下降。</p><p><strong>延时：</strong>JVM的GC持续时间也会影响到每次请求的响应时间。</p><h3>具体调优方法</h3><p><strong>调整堆内存空间减少FullGC：</strong>通过日志分析，堆内存基本被用完了，而且存在大量FullGC，这意味着我们的堆内存严重不足，这个时候我们需要调大堆内存空间。</p><pre><code>java -jar -Xms4g -Xmx4g heapTest-0.0.1-SNAPSHOT.jar
</code></pre><p>以下是各个配置项的说明：</p><ul>
<li>-Xms：堆初始大小；</li>
<li>-Xmx：堆最大值。</li>
</ul><p>调大堆内存之后，我们再来测试下性能情况，发现吞吐量提高了40%左右，响应时间也降低了将近50%。</p><p><img src="https://static001.geekbang.org/resource/image/5f/af/5fd7c3f198018cf5e789c25bd4f14caf.png?wh=686*389" alt=""></p><p>再查看GC日志，发现FullGC频率降低了，老年代的使用率只有16%了。</p><p><img src="https://static001.geekbang.org/resource/image/b9/2e/b924a13d8cb4e383b94e82d34125002e.jpeg?wh=1118*707" alt=""></p><p><strong>调整年轻代减少MinorGC：</strong>通过调整堆内存大小，我们已经提升了整体的吞吐量，降低了响应时间。那还有优化空间吗？我们还可以将年轻代设置得大一些，从而减少一些MinorGC（<a href="https://time.geekbang.org/column/article/107396" target="_blank" rel="noopener">第22讲</a>有通过降低Minor GC频率来提高系统性能的详解）。</p><pre><code>java -jar -Xms4g -Xmx4g -Xmn3g heapTest-0.0.1-SNAPSHOT.jar
</code></pre><p>再进行AB压测，发现吞吐量上去了。</p><p><img src="https://static001.geekbang.org/resource/image/55/04/55de34ab7eccaf9ad83bac846d0cbf04.png?wh=643*424" alt=""></p><p>再查看GC日志，发现MinorGC也明显降低了，GC花费的总时间也减少了。</p><p><img src="https://static001.geekbang.org/resource/image/75/5d/75f84993ba0c52d6d338d19dd4db1a5d.jpeg?wh=1113*707" alt=""></p><p><strong>设置Eden、Survivor区比例：</strong>在JVM中，如果开启 AdaptiveSizePolicy，则每次 GC 后都会重新计算 Eden、From Survivor和 To Survivor区的大小，计算依据是 GC 过程中统计的 GC 时间、吞吐量、内存占用量。这个时候SurvivorRatio默认设置的比例会失效。</p><p>在JDK1.8中，默认是开启AdaptiveSizePolicy的，我们可以通过-XX:-UseAdaptiveSizePolicy关闭该项配置，或显示运行-XX:SurvivorRatio=8将Eden、Survivor的比例设置为8:2。大部分新对象都是在Eden区创建的，我们可以固定Eden区的占用比例，来调优JVM的内存分配性能。</p><p>再进行AB性能测试，我们可以看到吞吐量提升了，响应时间降低了。</p><p><img src="https://static001.geekbang.org/resource/image/91/c2/91eb734ea45dc8a24b7938401eafb7c2.png?wh=598*417" alt=""></p><p><img src="https://static001.geekbang.org/resource/image/cf/fd/cfaef71dbc8ba4f149a6e134482370fd.jpeg?wh=1131*704" alt=""></p><h2>总结</h2><p><span class="orange">JVM内存调优通常和GC调优是互补的，</span>基于以上调优，我们可以继续对年轻代和堆内存的垃圾回收算法进行调优。这里可以结合上一讲的内容，一起完成JVM调优。</p><p>虽然分享了一些JVM内存分配调优的常用方法，但我还是建议你在进行性能压测后如果没有发现突出的性能瓶颈，就继续使用JVM默认参数，起码在大部分的场景下，默认配置已经可以满足我们的需求了。但满足不了也不要慌张，结合今天所学的内容去实践一下，相信你会有新的收获。</p><h2>思考题</h2><p>以上我们都是基于堆内存分配来优化系统性能的，但在NIO的Socket通信中，其实还使用到了堆外内存来减少内存拷贝，实现Socket通信优化。<span class="orange">你知道堆外内存是如何创建和回收的吗？</span></p><p>期待在留言区看到你的见解。也欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他一起讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/2a/54/c9990105.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>bro.</span>
  </div>
  <div class="_2_QraFYR_0">堆外内存创建有两种方式:1.使用ByteBuffer.allocateDirect()得到一个DirectByteBuffer对象,初始化堆外内存大小,里面会创建Cleaner对象,绑定当前this.DirectByteBuffer的回收,通过put,get传递进去Byte数组,或者序列化对象,Cleaner对象实现一个虚引用(当内存被回收时,会受到一个系统通知)当Full GC的时候,如果DirectByteBuffer标记为垃圾被回收,则Cleaner会收到通知调用clean()方法,回收改堆外内存DirectByteBuffer</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 回答很全面，赞！</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-22 17:53:15</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/17/11/6b/8034959a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>迎风劲草</span>
  </div>
  <div class="_2_QraFYR_0">老师，你的这个抢购场景下我理解是不是新生代越大越好，因为对象都是生命周期较短的对象。尽量在新生代中被回收掉。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 也不是越大越好，因为新生代过大，会导致minor gc的停顿时间过长。<br><br>我们知道，如果新生代很快就满了，会以担保的方式将新增的对象直接分配到老年代，这样增加了老年代回收的成本，这个成本跟具体的垃圾收集器相关。所以我们需要适当的调大年轻代，将对象尽量留在年轻代回收。<br><br>如果调整太大，我们知道每次Minor GC分为对象标记和复制两个阶段，并且都是STW的，如果对象过于庞大，有可能标记时间要大于复制时间，这样反而适得其反。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-18 21:38:02</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/f5/57/ce10fb1b.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>天天向上</span>
  </div>
  <div class="_2_QraFYR_0">如果你在线上环境或性能测试时，发现频繁的 GC，且是正常的对象创建和回收，这个时候就需要考虑调整 JVM 内存分配了。。有个问题，这个频率多久算频繁呢？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 线上正常情况下FullGC出现的频率是非常低的，几天一次，一般FullGC如果出现一天超过一次，就已经算频繁了。<br><br>做性能压测的时候，FullGC的频率会高一些，但也是仅限于个位数。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-01-04 22:59:22</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/7b/57/a9b04544.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>QQ怪</span>
  </div>
  <div class="_2_QraFYR_0">盲目增大堆内存可能会让吞吐量不增反减，堆内存大了，每次gc扫描对象也就越多也越需要花费时间，反而会适得其反</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的。合理设置堆内存大小，根据实际业务调整，不宜过大，也不宜过小。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-16 22:49:22</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/67/f4/9a1feb59.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>钱</span>
  </div>
  <div class="_2_QraFYR_0">课后思考及问题<br>1：JVM 内存分配不合理最直接的表现就是频繁的 GC，这会导致上下文切换等性能问题，从而降低系统的吞吐量、增加系统的响应时间。<br>频繁的GC，GC线程和应用线程会频繁的切入切出，所以，降低了系统的性能。<br>2：老师好，现在有这么一个问题，我们有一个定时任务跑一次大概会有2亿条数据一条数据大概40kb大小，一次大概7.4TB多的数据，分布式任务50台机器需要刷新2个多小时，我们需要持久化，为了提高性能做了异步发送MQ到另外的机器来持久化，不过MQ积压严重，数据跑一次耗时太长，有什么建议的优化思路嘛？拆分消息会加剧业务处理的复杂度，目前我能想到的是加机器加带宽。请老师给个优化的思考？<br></div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 优化传输性能，例如使用特定的数据结构序列化与反序列化传输数据（protobuff序列化），并且提高单台服务并行处理能力。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-11 23:17:51</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/a2/ec/205fd50c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>我又不乱来</span>
  </div>
  <div class="_2_QraFYR_0">超哥，有两个疑问。<br>当第一次创建对象的时候 eden 空间不足会进行一次minor gc把存活的对象放到from s区。如果这个时候from s放不下。会发生一次担保进入老年代吗？<br>当一次创建对象的时候eden空间不足进入from s区。当第二次创建对象的时候eden空间又不足了，这个时候会把，eden和第一次存在from s 区的对象进行gc 存活的放在 to s区，to s区空间不足，进行担保放入老年代？这样的理解对吗。<br></div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的，细节把握的很好！<br><br>前提是老年代有容量这些对象的空间，才会进行分配担保。如果老年代剩余空间小于每次minor gc晋升到老年代的平均值，则会发起一次Full GC。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-16 14:38:35</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/79/07/2f418316.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>恰饭哒</span>
  </div>
  <div class="_2_QraFYR_0">超哥好，我们经常发现生产环境内存使用超过90％持续3分钟，没有outofmer,<br>dump下来堆没有发现问题，这种情况每不确定几小时就会一次，求解答</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 你好，某一时间段高峰值的访问可能会有这种情况，JVM会最大可能进行对象的回收，防止内存溢出异常的发生。如果不是内存泄漏，或者瞬时并发量大大超过预期并发量的情况，几乎很少发生内存溢出异常。<br><br>建议结合内存持续占用率以及Full GC发生的频率来分析调优。<br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-16 20:54:46</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/15/03/c0fe1dbf.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>考休</span>
  </div>
  <div class="_2_QraFYR_0">根据老师的教程，在测试项目中，将年轻代的大小调整为3g，发现的确性能提升了，Mirror GC的次数也大大减少，但是Full GC的次数也明显多了几倍，这个是因为年轻代的空间过大，压缩了老年代的内存大小吗？<br>java -jar -Xms4g -Xmx4g -Xmn3g heapTest-0.0.1-SNAPSHOT.jar</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-11-13 17:17:42</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/17/8b/4b/15ab499a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>风轻扬</span>
  </div>
  <div class="_2_QraFYR_0">老师，如果允许分配担保机制失败。那即使老年代的空间不足以吃下年轻代的对象。jvm也会冒险进行minor gc的。gc之后，如果老年代还是吃不下对象，这个时候才会Full GC。那关闭这个分配担保机制，感觉好一点啊，反正有Full GC兜底呢😃</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 打开分配担保机制，是为了避免Full GC过于频繁。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-10 08:12:34</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/79/4b/740f91ca.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>-W.LI-</span>
  </div>
  <div class="_2_QraFYR_0">老师好!堆外缓存实在FGC的时候回收的吧。<br>AdaptiveSizePolicy这个参数是不是不太智能啊?我项目4G内存默认开启的AdaptiveSizePolicy。发现只给年轻代分配了136M内存。平时运行到没啥问题，没到定时任务的点就频繁FGC。每次定时任务执行完，都会往老年代推40多M，一天会堆300多M到老年代，也不见它把年轻代调大。用的parNew+CMS。后来把年轻代调整到1G(单次YGC耗时从20ms增加到了40ms)，每天老年代内存涨20M左右。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 这个会根据我们的内存创建大小合理分配内存，并不仅仅考虑对象晋升的问题，还会综合考虑回收停顿时间等因素。<br><br>针对某些特殊场景，我们可以手动来配置调优。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-16 08:59:29</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/1e/bd/84/06654c88.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>slofish</span>
  </div>
  <div class="_2_QraFYR_0">实战项目中真的发生大量gc. 增大内存也不能根本解决问题，应该明确什么原因导致这些对象不被回收，而不是盲目增加，指标不治本</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-06-30 23:28:43</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>刘梦春</span>
  </div>
  <div class="_2_QraFYR_0">堆内存最多4g  年轻代3g   1g的老年代怎么给年轻代做担保？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-01-21 19:50:05</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/15/dd/34/d8f198c8.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Levvy</span>
  </div>
  <div class="_2_QraFYR_0">最大堆内存1593M  还有124M 这俩数字是在哪看的，我怎么找不到</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 在jmap -heap pid运行之后，有一个MaxHeapSize，这个就是1953M，还有一个是NewSize以及OldSize，加起来就是初始化的124M大小。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-12-04 17:19:32</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/10/7b/eed9d6d6.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>小笨蛋</span>
  </div>
  <div class="_2_QraFYR_0">请问堆内存的分配有没有一个大概的标准😭既然都提到了不能太大也不能太小</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 需要根据自己的项目来具体做配置，如果不清除具体需要的配置大小，使用默认配置就可以了</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-17 21:56:34</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/42/4f/ff1ac464.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>又双叒叕是一年啊</span>
  </div>
  <div class="_2_QraFYR_0">你好，请问G1调优能不能也讲讲。主要应该注意些什么和cms这种调优的差异</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 嗯，在后面的答疑课堂中讲到了，有问题欢迎提出</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-29 00:38:09</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/18/db/f6/eac83ef4.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>披荆斩棘</span>
  </div>
  <div class="_2_QraFYR_0">超哥，我最近在学并行编程过程中，学master- work并行程序设计模式，运行书中给的示例，就是“用线程计算1到10000的3次方和”，我的电脑是8核20g内存，用一个线程和用5个线程去跑，跑出来时间差不多，非常疑惑，按道理时间是1线程跑的1&#47;5吧</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-11-22 20:18:42</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/9e/24/0d6a7987.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>aroll</span>
  </div>
  <div class="_2_QraFYR_0">netty4通过引用计数，来处理缓冲区的复用与回收</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-03-24 17:43:43</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/ff/e6/4b0ddfcf.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>李飞</span>
  </div>
  <div class="_2_QraFYR_0">老师，MinorGC频率多少比较合适呢？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-03-03 20:28:19</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/17/89/b1/9f9cfc19.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>猪大强</span>
  </div>
  <div class="_2_QraFYR_0">过分的调大堆内存，会增加MinorGC扫描的时间的，在高并发的情况下如何解决呢？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-12-22 17:09:50</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Geek_323c91</span>
  </div>
  <div class="_2_QraFYR_0">不知道能不能收到回复,我有个以为,网上查了很多资料 也没得到一个答案,-c 1000 -n 100000 和 -c 100000 和-n 10000的区别是什么 自己压测 怎么调整这2个值</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: -n表示请求次数  -c表示并发数，-c 1000 -n 100000表示总的请求100000次，且并发用户数为1000。-c 100000 和-n 10000表示总的请求10000次，并发用户数为100000。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-05-19 17:21:11</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/24%20_%20%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96JVM%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%EF%BC%9F/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/23 _ 如何优化垃圾回收机制？" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/23%20_%20%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6%EF%BC%9F/">极客时间/Java性能调优实战/23 _ 如何优化垃圾回收机制？</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/23%20_%20%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6%EF%BC%9F/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:31.003Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="23 _ 如何优化垃圾回收机制？" src="https://static001.geekbang.org/resource/audio/bc/92/bcd855b07fef56d5995519f1505e5792.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>我们知道，在Java开发中，开发人员是无需过度关注对象的回收与释放的，JVM的垃圾回收机制可以减轻不少工作量。但完全交由JVM回收对象，也会增加回收性能的不确定性。在一些特殊的业务场景下，不合适的垃圾回收算法以及策略，都有可能导致系统性能下降。</p><p><span class="orange">面对不同的业务场景，垃圾回收的调优策略也不一样。</span>例如，在对内存要求苛刻的情况下，需要提高对象的回收效率；在CPU使用率高的情况下，需要降低高并发时垃圾回收的频率。可以说，垃圾回收的调优是一项必备技能。</p><p>这讲我们就把这项技能的学习进行拆分，看看回收（后面简称GC）的算法有哪些，体现GC算法好坏的指标有哪些，又如何根据自己的业务场景对GC策略进行调优？</p><h2>垃圾回收机制</h2><p>掌握GC算法之前，我们需要先弄清楚3个问题。第一，回收发生在哪里？第二，对象在什么时候可以被回收？第三，如何回收这些对象？</p><h3>1. 回收发生在哪里？</h3><p>JVM的内存区域中，程序计数器、虚拟机栈和本地方法栈这3个区域是线程私有的，随着线程的创建而创建，销毁而销毁；栈中的栈帧随着方法的进入和退出进行入栈和出栈操作，每个栈帧中分配多少内存基本是在类结构确定下来的时候就已知的，因此这三个区域的内存分配和回收都具有确定性。</p><!-- [[[read_end]]] --><p>那么垃圾回收的重点就是关注堆和方法区中的内存了，堆中的回收主要是对象的回收，方法区的回收主要是废弃常量和无用的类的回收。</p><h3>2. 对象在什么时候可以被回收？</h3><p>那JVM又是怎样判断一个对象是可以被回收的呢？<span class="orange">一般一个对象不再被引用，就代表该对象可以被回收。</span>目前有以下两种算法可以判断该对象是否可以被回收。</p><p><strong>引用计数算法：</strong>这种算法是通过一个对象的引用计数器来判断该对象是否被引用了。每当对象被引用，引用计数器就会加1；每当引用失效，计数器就会减1。当对象的引用计数器的值为0时，就说明该对象不再被引用，可以被回收了。这里强调一点，虽然引用计数算法的实现简单，判断效率也很高，但它存在着对象之间相互循环引用的问题。</p><p><strong>可达性分析算法：</strong>GC  Roots  是该算法的基础，GC Roots是所有对象的根对象，在JVM加载时，会创建一些普通对象引用正常对象。这些对象作为正常对象的起始点，在垃圾回收时，会从这些GC Roots开始向下搜索，当一个对象到 GC Roots 没有任何引用链相连时，就证明此对象是不可用的。目前HotSpot虚拟机采用的就是这种算法。</p><p>以上两种算法都是通过引用来判断对象是否可以被回收。在 JDK 1.2 之后，Java 对引用的概念进行了扩充，将引用分为了以下四种：</p><p><img src="https://static001.geekbang.org/resource/image/5c/0a/5c671c5ae73cbb8bc14b38d9e871530a.jpg?wh=1358*488" alt=""></p><h3>3. 如何回收这些对象？</h3><p>了解完Java程序中对象的回收条件，那么垃圾回收线程又是如何回收这些对象的呢？JVM垃圾回收遵循以下两个特性。</p><p><strong>自动性：</strong>Java提供了一个系统级的线程来跟踪每一块分配出去的内存空间，当JVM处于空闲循环时，垃圾收集器线程会自动检查每一块分配出去的内存空间，然后自动回收每一块空闲的内存块。</p><p><strong>不可预期性：</strong>一旦一个对象没有被引用了，该对象是否立刻被回收呢？答案是不可预期的。我们很难确定一个没有被引用的对象是不是会被立刻回收掉，因为有可能当程序结束后，这个对象仍在内存中。</p><p>垃圾回收线程在JVM中是自动执行的，Java程序无法强制执行。我们唯一能做的就是通过调用System.gc方法来"建议"执行垃圾收集器，但是否可执行，什么时候执行？仍然不可预期。</p><h2>GC算法</h2><p>JVM提供了不同的回收算法来实现这一套回收机制，通常垃圾收集器的回收算法可以分为以下几种：</p><p><img src="https://static001.geekbang.org/resource/image/3f/b9/3f4316c41d4ffb27e5a36db5f2641db9.jpg?wh=1314*404" alt=""></p><p>如果说收集算法是内存回收的方法论，那么垃圾收集器就是内存回收的具体实现，JDK1.7 update14 之后Hotspot虚拟机所有的回收器整理如下（以下为服务端垃圾收集器）：</p><p><img src="https://static001.geekbang.org/resource/image/28/74/2824581e7c94a3a94b2b0abb1d348974.jpg?wh=1896*702" alt=""></p><p>其实在JVM规范中并没有明确GC的运作方式，各个厂商可以采用不同的方式实现垃圾收集器。<span class="orange">我们可以通过JVM工具查询当前JVM使用的垃圾收集器类型，</span>首先通过ps命令查询出进程ID，再通过jmap -heap ID查询出JVM的配置信息，其中就包括垃圾收集器的设置类型。</p><p><img src="https://static001.geekbang.org/resource/image/95/97/953dc139ff9035b41d06d4a400395e97.png?wh=476*627" alt=""></p><h2>GC性能衡量指标</h2><p>一个垃圾收集器在不同场景下表现出的性能也不一样，那么如何评价一个垃圾收集器的性能好坏呢？我们可以借助一些指标。</p><p><strong>吞吐量：</strong>这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算GC的吞吐量：系统总运行时间=应用程序耗时+GC耗时。如果系统运行了100分钟，GC耗时1分钟，则系统吞吐量为99%。<span class="orange">GC的吞吐量一般不能低于95%。</span></p><p><strong>停顿时间：</strong>指垃圾收集器正在运行时，应用程序的暂停时间。对于串行回收器而言，停顿时间可能会比较长；而使用并发回收器，由于垃圾收集器和应用程序交替运行，程序的停顿时间就会变短，但其效率很可能不如独占垃圾收集器，系统的吞吐量也很可能会降低。</p><p><strong>垃圾回收频率：</strong>多久发生一次指垃圾回收呢？通常垃圾回收的频率越低越好，增大堆内存空间可以有效降低垃圾回收发生的频率，但同时也意味着堆积的回收对象越多，最终也会增加回收时的停顿时间。所以我们只要适当地增大堆内存空间，保证正常的垃圾回收频率即可。</p><h2>查看&amp;分析GC日志</h2><p>已知了性能衡量指标，现在我们需要通过工具查询GC相关日志，统计各项指标的信息。首先，我们需要通过JVM参数预先设置GC日志，通常有以下几种JVM参数设置：</p><pre><code>-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳（以基准时间的形式）
-XX:+PrintGCDateStamps 输出GC的时间戳（以日期的形式，如 2013-05-04T21:53:59.234+0800）
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径
</code></pre><p>这里使用如下参数来打印日志：</p><pre><code>-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs
</code></pre><p>打印后的日志为：</p><p><img src="https://static001.geekbang.org/resource/image/58/58/58d74b6e3e68edf9b595287686b42b58.png?wh=1333*319" alt=""></p><p>上图是运行很短时间的GC日志，如果是长时间的GC日志，我们很难通过文本形式去查看整体的GC性能。此时，我们可以通过<a href="https://sourceforge.net/projects/gcviewer/" target="_blank" rel="noopener">GCViewer</a>工具打开日志文件，图形化界面查看整体的GC性能，如下图所示：</p><p><img src="https://static001.geekbang.org/resource/image/69/79/69db951663299d342aad572d911b0279.jpeg?wh=1928*1048" alt=""><img src="https://static001.geekbang.org/resource/image/f9/37/f95d5db87d20068085b6d67cd6822d37.png?wh=1928*1048" alt=""></p><p>通过工具，我们可以看到吞吐量、停顿时间以及GC的频率，从而可以非常直观地了解到GC的性能情况。</p><p>这里我再推荐一个比较好用的GC日志分析工具，<a href="https://www.gceasy.io/index.jsp" target="_blank" rel="noopener">GCeasy</a>是一款非常直观的GC日志分析工具，我们可以将日志文件压缩之后，上传到GCeasy官网即可看到非常清楚的GC日志分析结果：</p><p><img src="https://static001.geekbang.org/resource/image/ab/22/ab3119a73f313d20a4aa0cee02e84022.jpeg?wh=1190*405" alt=""><img src="https://static001.geekbang.org/resource/image/ef/27/ef85b02537b9c970d55d3bbd5a3e3427.jpeg?wh=1197*820" alt=""><img src="https://static001.geekbang.org/resource/image/71/95/71e8f8922bc7045a7b52e5a6dff82595.jpeg?wh=1173*700" alt=""><img src="https://static001.geekbang.org/resource/image/83/be/834d779d27afbee8c70219c1628f0bbe.jpeg?wh=1190*810" alt=""><img src="https://static001.geekbang.org/resource/image/83/ba/830069547013fdcbbb74c1e9b75a77ba.jpeg?wh=1199*631" alt=""><img src="https://static001.geekbang.org/resource/image/63/8e/638ede71247b855b50e04a25564f268e.jpeg?wh=1215*192" alt=""></p><h2>GC调优策略</h2><p>找出问题后，就可以进行调优了，下面介绍几种常用的GC调优策略。</p><h3>1. 降低Minor GC频率</h3><p>通常情况下，由于新生代空间较小，Eden区很快被填满，就会导致频繁Minor GC，因此我们可以通过增大新生代空间来降低Minor GC的频率。</p><p>可能你会有这样的疑问，扩容Eden区虽然可以减少Minor GC的次数，但不会增加单次Minor GC的时间吗？如果单次Minor GC的时间增加，那也很难达到我们期待的优化效果呀。</p><p>我们知道，单次Minor  GC时间是由两部分组成：T1（扫描新生代）和T2（复制存活对象）。假设一个对象在Eden区的存活时间为500ms，Minor  GC的时间间隔是300ms，那么正常情况下，Minor  GC的时间为 ：T1+T2。</p><p>当我们增大新生代空间，Minor  GC的时间间隔可能会扩大到600ms，此时一个存活500ms的对象就会在Eden区中被回收掉，此时就不存在复制存活对象了，所以再发生Minor  GC的时间为：两次扫描新生代，即2T1。</p><p><span class="orange">可见，扩容后，Minor GC时增加了T1，但省去了T2的时间。</span>通常在虚拟机中，复制对象的成本要远高于扫描成本。</p><p>如果在堆内存中存在较多的长期存活的对象，此时增加年轻代空间，反而会增加Minor  GC的时间。如果堆中的短期对象很多，那么扩容新生代，单次Minor GC时间不会显著增加。因此，单次Minor GC时间更多取决于GC后存活对象的数量，而非Eden区的大小。</p><h3>2. 降低Full GC的频率</h3><p>通常情况下，由于堆内存空间不足或老年代对象太多，会触发Full GC，频繁的Full  GC会带来上下文切换，增加系统的性能开销。我们可以使用哪些方法来降低Full GC的频率呢？</p><p><strong>减少创建大对象：</strong>在平常的业务场景中，我们习惯一次性从数据库中查询出一个大对象用于web端显示。例如，我之前碰到过一个一次性查询出60个字段的业务操作，这种大对象如果超过年轻代最大对象阈值，会被直接创建在老年代；即使被创建在了年轻代，由于年轻代的内存空间有限，通过Minor GC之后也会进入到老年代。这种大对象很容易产生较多的Full GC。</p><p>我们可以将这种大对象拆解出来，首次只查询一些比较重要的字段，如果还需要其它字段辅助查看，再通过第二次查询显示剩余的字段。</p><p><strong>增大堆内存空间：</strong>在堆内存不足的情况下，增大堆内存空间，且设置初始化堆内存为最大堆内存，也可以降低Full GC的频率。</p><h3>选择合适的GC回收器</h3><p>假设我们有这样一个需求，要求每次操作的响应时间必须在500ms以内。这个时候我们一般会选择响应速度较快的GC回收器，CMS（Concurrent Mark Sweep）回收器和G1回收器都是不错的选择。</p><p>而当我们的需求对系统吞吐量有要求时，就可以选择Parallel Scavenge回收器来提高系统的吞吐量。</p><h2>总结</h2><p>今天的内容比较多，最后再强调几个重点。</p><p>垃圾收集器的种类很多，我们可以将其分成两种类型，一种是响应速度快，一种是吞吐量高。通常情况下，CMS和G1回收器的响应速度快，Parallel Scavenge回收器的吞吐量高。</p><p>在JDK1.8环境下，默认使用的是Parallel Scavenge（年轻代）+Serial Old（老年代）垃圾收集器，你可以通过文中介绍的查询JVM的GC默认配置方法进行查看。</p><p>通常情况，JVM是默认垃圾回收优化的，在没有性能衡量标准的前提下，尽量避免修改GC的一些性能配置参数。如果一定要改，那就必须基于大量的测试结果或线上的具体性能来进行调整。</p><h2>思考题</h2><p><span class="orange">以上我们讲到了CMS和G1回收器，你知道G1是如何实现更好的GC性能的吗？</span></p><p>期待在留言区看到你的见解。也欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他一起讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/17/27/ec30d30a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Jxin</span>
  </div>
  <div class="_2_QraFYR_0">1.7及前的都还好说，毕竟大部分开发都拜读过&lt;深入理解jvm&gt;。<br>回归整体，记得有点模糊了，如果有错误还请老师指正。首先cms在1.9已经被标记为废弃，主要原因在于标记清除下的悬浮内存，导致内存空间碎片化，进而导致fullGC的发生。不过其并行执行垃圾回收的性能还是值得认可的，至少1.9后主推的G1在常规情况下也是不如它的效率好的。接下来，说下G1，拼G1的堆内存结构比较特殊，虽然也有年代划分，但从物理角度上却不一样。G1将整块内存分配成若干个同等大小的reg。新生代（两个sub区加ed区）和老年代各自由不同数量的reg组成。垃圾回收的算法应该算是标记整理。所以其规避了cms内存碎片化的问题，大大降低了fullGC的频率。所以它虽然常态性能略输于cms但却没有cms特殊情况下的极端性能问题，总体更稳定。值得一提的是G1中各代的内存区域里reg间不一定是连续的，所以对于cpu缓存加载机制并不是特别友好，而且大对象占据超过一个reg时还带来内存浪费的问题。所以总的来说1.8可以用G1但得考虑场景，首先这个内存空间要大，保证每个reg尽量大，以减少内存浪费，保守估计8g以上用g1。实际公司很少会去升级jdk版本，大部分都是1.8，好在oracle一些1.9 10 11 12的特性都有以补丁的方式落到1.8。所以1.8还是比较安全实用的，虽然我们公司还是1.7，推不动哈。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 赞，Region这块 Jxin讲解的通俗易懂。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-13 14:05:06</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/b3/c5/7fc124e2.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Liam</span>
  </div>
  <div class="_2_QraFYR_0">1 minor gc是否会导致stop the world？<br>2 major gc什么时候会发生，它和full gc的区别是什么？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: Liam提出的这两个问题非常好。<br>1、不管什么GC，都会发送stop the world，区别是发生的时间长短。而这个时间跟垃圾收集器又有关系，Serial、PartNew、Parallel Scavenge收集器无论是串行还是并行，都会挂起用户线程，而CMS和G1在并发标记时，是不会挂起用户线程，但其他时候一样会挂起用户线程，stop the world的时间相对来说小很多了。<br><br>2、major gc很多参考资料指的是等价于full gc，我们也可以发现很多性能监测工具中只有minor gc和full gc。<br>一般情况下，一次full gc将会对年轻代、老年代以及元空间、堆外内存进行垃圾回收。而触发Full GC的原因有很多：<br>a、当年轻代晋升到老年代的对象大小比目前老年代剩余的空间大小还要大时，此时会触发Full GC；<br>b、当老年代的空间使用率超过某阈值时，此时会触发Full GC;<br>c、当元空间不足时（JDK1.7永久代不足），也会触发Full GC;<br>d、当调用System.gc()也会安排一次Full GC;<br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-14 10:25:47</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/a2/ec/205fd50c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>我又不乱来</span>
  </div>
  <div class="_2_QraFYR_0">超哥，我建议可以分享一下那些对象可以作为gc root的对象，为什么这些对象可以做为gc root对象？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 在Java语言里，可作为GC Root对象的包括如下几种： 1. Java虚拟机栈中的引用的对象 ； 2. 方法区中的类静态属性引用的对象 ； 3. 方法区中的常量引用的对象 ； 4. 本地方法栈中JNI的引用的对象。<br><br>我们知道，垃圾回收一般是回收堆和方法区的对象，而堆中的对象在正常情况下，一般是通过常量、全局变量、静态变量等间接引用堆中的对象，所以这些可以作为GC Root。<br><br>在任何上述的GCRoot中，有引用可以指向时，我们称之为对象可达。<br><br><br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-13 08:35:57</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/7b/57/a9b04544.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>QQ怪</span>
  </div>
  <div class="_2_QraFYR_0">G1与CMS的优势在于以下几点：<br>1、并行与并发：G1能够更充分利用多CPU、多核环境运行<br>2、分代收集：G1虽然也用了分代概念，但相比其他收集器需要配合不同收集协同工作，但G1收集器能够独立管理整个堆<br>3、空间管理：与CMS的标记一清理算法不同，G1从整体上基于标记一整理算法，将整个Java堆划分为多个大小相等的独立区域（Region）,这种算法能够在运行过程中不产生内存碎片<br>4、可预测的停顿：降低停顿时间是G1和CMS共同目标，但是G1追求低停顿外，还能建立可预测的停顿时间模型，能让使用者明确指定一个长度为M毫秒的时间片段内，消耗在垃圾收集器上的时间不得超过N毫秒。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 赞。<br><br>理解G1中的几个重要概念：Region、SATB、RSet以及Pause Prediction Model，能更好的理解G1相对CMS的一些具体优势在哪里了。<br><br><br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-13 14:12:25</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/b5/0d/0e65dee6.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>FelixFly</span>
  </div>
  <div class="_2_QraFYR_0">在 JDK1.8 环境下，默认使用的是 Parallel Scavenge（年轻代）+Serial Old（老年代）垃圾收集器。老师，这个地方你写错了吧，用jinfo -flags 进程ID打印出-XX:+UseParallelGC是使用的这个，这个在官方文档说的是-XX:+UseParallelGC启用，-XX:+UseParallelOldGC这个会自动启用，应该为Parallel Scavenge（年轻代）+Parallel Old（老年代）垃圾收集器<br>官网参数说明（查看的是linux下的）<br>-XX:+UseParallelGC<br>Enables the use of the parallel scavenge garbage collector (also known as the throughput collector) to improve the performance of your application by leveraging multiple processors.<br><br>By default, this option is disabled and the collector is chosen automatically based on the configuration of the machine and type of the JVM. If it is enabled, then the -XX:+UseParallelOldGC option is automatically enabled, unless you explicitly disable it.</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-11-19 12:37:37</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/14/58/84/fd5031e5.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>别忘微笑</span>
  </div>
  <div class="_2_QraFYR_0">超哥，一个web应用，多久一次Full GC才算正常呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 需要根据具体的业务来分析，正常小对象且请求平缓的应用服务中，几天一次较为正常。如果有大量大对象创建或者承受高并发场景的服务，Full GC可能会更频繁。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-15 10:35:24</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/10/a6/4d2c933e.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>K</span>
  </div>
  <div class="_2_QraFYR_0">老师好，我有两个问题想问一下。1.比如说看到jvm的参数，-XX:+UseConcMarkSweepGC，这个参数是单独指定了老年代的收集器呢，还是年轻代、老年代都指定了？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 运行XX:+UseConcMarkSweepGC命令，默认会指定年轻代和老年代的垃圾收集器，分别为ParNewGC和ConcMarkSweepGC两种收集器。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-04 23:41:48</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/4c/59/c75cb36d.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>N</span>
  </div>
  <div class="_2_QraFYR_0">老师您好，公司ES服务器设置最大最小堆内存26个G，G1GC,  XX:MaxGCPauseMillis =500，一段时间内old gc 都会稳定在500ms以内，但每天总会有1-2次old gc 时间很长，大概3000ms.请问该如何优化呢？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 设置的并发标记线程数量是多少呢？可以通过-XX:ConcGCThreads尝试适当调整这个数量，为服务器CPU核数的1&#47;4，可以提高并发标记的效率。<br><br>由于JVM 垃圾回收和内存分配这块的调优错综复杂，需要我们再结合服务器上跑的相关的业务以及GC日志逐步调优。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-20 15:27:59</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/67/f4/9a1feb59.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>钱</span>
  </div>
  <div class="_2_QraFYR_0">很棒，很清晰的讲明白了什么是垃圾？有几种收集垃圾的方式？有几种具体的垃圾收集器？<br>请问老师收集垃圾具体是一个什么原理？标记对应的内存可用了，还是将对应的数据都一个个清空了？<br></div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 标记可以回收的对象，然后在垃圾回收时将对象回收</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-11 22:20:25</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/36/d2/c7357723.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>发条橙子 。</span>
  </div>
  <div class="_2_QraFYR_0">超哥 我想问下，相同的方法多次执行，再没有JIT编译的前提下，每一次执行都会进行一次解释执行莫？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-24 18:27:02</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/1e/3a/5b21c01c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>nightmare</span>
  </div>
  <div class="_2_QraFYR_0">老师看完有两个疑问，第一这么查看minor gc回收之后 eden区存活对象的多少，第二 jmap -heap pid在图中只能看年轻代parallel gc看不到老年代的是什么垃圾回收器    对于提问 cms垃圾回收器还是分老年代和年轻代回收分多个阶段有和程序并行的阶段也有stop the world阶段 回收一整块老年代时间比较久，而 gc把年轻代和老年代也有划分，不过拆成一个region了，对region的回收成本低，而且会判断那些region回收的对象更多，而且cms要经过多次full gc才可能把不用的内存归还给操作系统  而g1只需要一次full gc就可以</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 我们可以通过jstat -gc pid interval查看每次GC之后，具体的每一个分区的内存使用率变化情况。我们可以通过查看JVM设置参数来查看具体的垃圾收集器的设置参数，使用的方式有很多，例如jcmd  pid VM.flags可以查看到相关的设置参数。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-13 02:07:36</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLmxBGkh4RfIaiakV14ErYFsic0dfQgtZrD5hicVJQn5T5AQ95aqhibBoQVs02OWuFOTWMV1TAYkpX2rw/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Policy</span>
  </div>
  <div class="_2_QraFYR_0">老师，这个没懂：所以再发生 Minor  GC 的时间为：两次扫描新生代，即 2T1。是因为时间间隔长了一倍，所以扫描对象的数量多了一倍？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2022-06-26 21:56:58</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/14/83/19/0a3fe8c1.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Evan</span>
  </div>
  <div class="_2_QraFYR_0">CMS 收集器 有一个浮动垃圾概念， 可能出现“Concurrent Mode Failure”  导致Full GC， 能否解决一下本质原因？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 出现“Concurrent Mode Failure”，我们可以使用多种方式来解决，例如将CMS触发的阈值调低，提前触发CMS GC，释放老年代内存空间，满足新生代对象提升到老年代，或应用服务直接分配大对象到老年代。我们也可以尝试调大年轻代和老年代的大小，满足对应对象的分配空间。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-03-21 21:14:28</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/c2/fe/038a076e.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>阿卧</span>
  </div>
  <div class="_2_QraFYR_0">cms回收器只能在老年代使用吗。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: CMS只会回收老年代和永久代（元数据区）的对象。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-02-17 11:40:03</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/10/a6/4d2c933e.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>K</span>
  </div>
  <div class="_2_QraFYR_0">第二个问题就是，比如说我指定了：-XX:+UseConcMarkSweepGC，也指定了：-XX:+UseParallelOldGC，那么年轻代、老年代分别是用了什么垃圾回收器呢？麻烦老师解答一下，谢谢！</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 我觉得前者会被后者覆盖，可以自己试试，然后通过指令查询相关的生效参数。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-04 23:43:42</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/1e/3a/5b21c01c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>nightmare</span>
  </div>
  <div class="_2_QraFYR_0">老师，查看minor gc存活对象的命令是什么呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 具体存活的对象是在随时变化的，很难追踪，目前只能通过各个区域的大小来分析GC效率。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-15 11:07:20</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/79/4b/740f91ca.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>-W.LI-</span>
  </div>
  <div class="_2_QraFYR_0">老师好!Serial Old不是标记整理算法么?Serial new是复制吧。我记得年轻代都是采用复制的，老年代除了CMS是标记清除(存在内存碎片)别的好像都是标记整理整理吧。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是标记整理算法。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-13 21:32:41</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/17/f6/50/34f89402.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>🦄</span>
  </div>
  <div class="_2_QraFYR_0">我们用的是parallel old垃圾收集器，这个Full GC的触发条件可以配置么？ 老年代内存不停的增加，有没有可能年轻带最后一次回收导致JVM做Fullgc，整个内存不够导致应用直接挂掉呀，我发现了这种现象，就是一直不做fullgc，然后最后做的那一次应用挂了，这种怎么排查问题呀</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2022-06-09 23:22:47</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/0e/bb/358c293c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>杨雪宁</span>
  </div>
  <div class="_2_QraFYR_0">老师，一直有个疑问，有说CMS适用于10G以内的内存，这个说法对吗？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2022-03-29 22:05:12</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/28/91/91/428a27a3.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>平民人之助</span>
  </div>
  <div class="_2_QraFYR_0">像替换架构组件这个问题，如何去推动并且规避人际风险呢？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-07-10 23:11:03</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/23%20_%20%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6%EF%BC%9F/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/22 _ 深入JVM即时编译器JIT，优化Java编译" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/22%20_%20%E6%B7%B1%E5%85%A5JVM%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91%E5%99%A8JIT%EF%BC%8C%E4%BC%98%E5%8C%96Java%E7%BC%96%E8%AF%91/">极客时间/Java性能调优实战/22 _ 深入JVM即时编译器JIT，优化Java编译</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/22%20_%20%E6%B7%B1%E5%85%A5JVM%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91%E5%99%A8JIT%EF%BC%8C%E4%BC%98%E5%8C%96Java%E7%BC%96%E8%AF%91/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:25.253Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="22 _ 深入JVM即时编译器JIT，优化Java编译" src="https://static001.geekbang.org/resource/audio/be/09/be9221f38d34da3c01daa85acfc12a09.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>说到编译，我猜你一定会想到 .java文件被编译成 .class文件的过程，这个编译我们一般称为前端编译。Java的编译和运行过程非常复杂，除了前端编译，还有运行时编译。由于机器无法直接运行Java生成的字节码，所以在运行时，JIT或解释器会将字节码转换成机器码，这个过程就叫运行时编译。</p><p>类文件在运行时被进一步编译，它们可以变成高度优化的机器代码，由于C/C++编译器的所有优化都是在编译期间完成的，运行期间的性能监控仅作为基础的优化措施则无法进行，例如，调用频率预测、分支频率预测、裁剪未被选择的分支等，而Java在运行时的再次编译，就可以进行基础的优化措施。因此，JIT编译器可以说是JVM中运行时编译最重要的部分之一。</p><p>然而许多Java开发人员对JIT编译器的了解并不多，不深挖其工作原理，也不深究如何检测应用程序的即时编译情况，线上发生问题后很难做到从容应对。<span class="orange">今天我们就来学习运行时编译如何实现对Java代码的优化。</span></p><h2>类编译加载执行过程</h2><p>在这之前，我们先了解下Java从编译到运行的整个过程，为后面的学习打下基础。请看下图：</p><p><img src="https://static001.geekbang.org/resource/image/8d/17/8d4ec9c73ec37d69adb105aa7d052717.jpg?wh=2172*410" alt=""></p><h3>类编译</h3><p>在编写好代码之后，我们需要将 .java文件编译成 .class文件，才能在虚拟机上正常运行代码。文件的编译通常是由JDK中自带的Javac工具完成，一个简单的 .java文件，我们可以通过javac命令来生成 .class文件。</p><!-- [[[read_end]]] --><p>下面我们通过javap（ <a href="https://time.geekbang.org/column/article/101244" target="_blank" rel="noopener">第12讲</a> 讲过如何使用javap反编译命令行）反编译来看看一个class文件结构中主要包含了哪些信息：</p><p><img src="https://static001.geekbang.org/resource/image/60/7e/605dbbcfbbfcc09143d7d9e7fed5ac7e.png?wh=1197*2671" alt=""></p><p>看似一个简单的命令执行，前期编译的过程其实是非常复杂的，包括词法分析、填充符号表、注解处理、语义分析以及生成class文件，这个过程我们不用过多关注。只要从上图中知道，<span class="orange">编译后的字节码文件主要包括常量池和方法表集合这两部分</span>就可以了。</p><p>常量池主要记录的是类文件中出现的字面量以及符号引用。字面常量包括字符串常量（例如String str=“abc”，其中"abc"就是常量），声明为final的属性以及一些基本类型（例如，范围在-127-128之间的整型）的属性。符号引用包括类和接口的全限定名、类引用、方法引用以及成员变量引用（例如String str=“abc”，其中str就是成员变量引用）等。</p><p>方法表集合中主要包含一些方法的字节码、方法访问权限（public、protect、prviate等）、方法名索引（与常量池中的方法引用对应）、描述符索引、JVM执行指令以及属性集合等。</p><h3>类加载</h3><p>当一个类被创建实例或者被其它对象引用时，虚拟机在没有加载过该类的情况下，会通过类加载器将字节码文件加载到内存中。</p><p>不同的实现类由不同的类加载器加载，JDK中的本地方法类一般由根加载器（Bootstrp loader）加载进来，JDK中内部实现的扩展类一般由扩展加载器（ExtClassLoader ）实现加载，而程序中的类文件则由系统加载器（AppClassLoader ）实现加载。</p><p>在类加载后，class类文件中的常量池信息以及其它数据会被保存到JVM内存的方法区中。</p><h3>类连接</h3><p>类在加载进来之后，会进行连接、初始化，最后才会被使用。在连接过程中，又包括验证、准备和解析三个部分。</p><p><strong>验证：</strong>验证类符合Java规范和JVM规范，在保证符合规范的前提下，避免危害虚拟机安全。</p><p><strong>准备：</strong>为类的静态变量分配内存，初始化为系统的初始值。对于final static修饰的变量，直接赋值为用户的定义值。例如，private final static int value=123，会在准备阶段分配内存，并初始化值为123，而如果是 private static int value=123，这个阶段value的值仍然为0。</p><p><strong>解析：</strong>将符号引用转为直接引用的过程。我们知道，在编译时，Java类并不知道所引用的类的实际地址，因此只能使用符号引用来代替。类结构文件的常量池中存储了符号引用，包括类和接口的全限定名、类引用、方法引用以及成员变量引用等。如果要使用这些类和方法，就需要把它们转化为JVM可以直接获取的内存地址或指针，即直接引用。</p><h3>类初始化</h3><p>类初始化阶段是类加载过程的最后阶段，在这个阶段中，JVM首先将执行构造器&lt;clinit&gt;方法，编译器会在将 .java 文件编译成 .class 文件时，收集所有类初始化代码，包括静态变量赋值语句、静态代码块、静态方法，收集在一起成为 &lt;clinit&gt;() 方法。</p><p>初始化类的静态变量和静态代码块为用户自定义的值，初始化的顺序和Java源码从上到下的顺序一致。例如：</p><pre><code>private static int i=1；
static{
  i=0;
}
public static void main(String [] args){
  System.out.println(i);
}

</code></pre><p>此时运行结果为：</p><pre><code>0
</code></pre><p>再来看看以下代码：</p><pre><code>static{
  i=0;
}
private static int i=1；
public static void main(String [] args){
  System.out.println(i);
}
</code></pre><p>此时运行结果为：</p><pre><code>1
</code></pre><p>子类初始化时会首先调用父类的 &lt;clinit&gt;() 方法，再执行子类的&lt;clinit&gt;() 方法，运行以下代码：</p><pre><code>public class Parent{
  public static String parentStr= &quot;parent static string&quot;;
  static{
    System.out.println(&quot;parent static fields&quot;);
    System.out.println(parentStr);
  }
  public Parent(){
    System.out.println(&quot;parent instance initialization&quot;);
 }
}

public class Sub extends Parent{
  public static String subStr= &quot;sub static string&quot;;
  static{
    System.out.println(&quot;sub static fields&quot;);
    System.out.println(subStr);
  }

  public Sub(){
    System.out.println(&quot;sub instance initialization&quot;);
  }
 
  public static void main(String[] args){
    System.out.println(&quot;sub main&quot;);
    new Sub();
 }
}
</code></pre><p>运行结果：</p><pre><code>parent static fields
parent static string
sub static fields
sub static string
sub main
parent instance initialization
sub instance initialization
</code></pre><p>JVM 会保证 &lt;clinit&gt;() 方法的线程安全，保证同一时间只有一个线程执行。</p><p>JVM在初始化执行代码时，如果实例化一个新对象，会调用&lt;init&gt;方法对实例变量进行初始化，并执行对应的构造方法内的代码。</p><h2>即时编译</h2><p>初始化完成后，类在调用执行过程中，执行引擎会把字节码转为机器码，然后在操作系统中才能执行。<span class="orange">在字节码转换为机器码的过程中，虚拟机中还存在着一道编译，那就是即时编译。</span></p><p>最初，虚拟机中的字节码是由解释器（ Interpreter ）完成编译的，当虚拟机发现某个方法或代码块的运行特别频繁的时候，就会把这些代码认定为“热点代码”。</p><p>为了提高热点代码的执行效率，在运行时，即时编译器（JIT）会把这些代码编译成与本地平台相关的机器码，并进行各层次的优化，然后保存到内存中。</p><h3>即时编译器类型</h3><p>在HotSpot虚拟机中，内置了两个JIT，分别为C1编译器和C2编译器，这两个编译器的编译过程是不一样的。</p><p>C1编译器是一个简单快速的编译器，主要的关注点在于局部性的优化，适用于执行时间较短或对启动性能有要求的程序，例如，GUI应用对界面启动速度就有一定要求。</p><p>C2编译器是为长期运行的服务器端应用程序做性能调优的编译器，适用于执行时间较长或对峰值性能有要求的程序。根据各自的适配性，这两种即时编译也被称为Client Compiler和Server Compiler。</p><p>在Java7之前，需要根据程序的特性来选择对应的JIT，虚拟机默认采用解释器和其中一个编译器配合工作。</p><p>Java7引入了分层编译，这种方式综合了C1的启动性能优势和C2的峰值性能优势，我们也可以通过参数 “-client”“-server” 强制指定虚拟机的即时编译模式。<strong>分层编译将JVM的执行状态分为了5个层次：</strong></p><ul>
<li>第0层：程序解释执行，默认开启性能监控功能（Profiling），如果不开启，可触发第二层编译；</li>
<li>第1层：可称为C1编译，将字节码编译为本地代码，进行简单、可靠的优化，不开启Profiling；</li>
<li>第2层：也称为C1编译，开启Profiling，仅执行带方法调用次数和循环回边执行次数profiling的C1编译；</li>
<li>第3层：也称为C1编译，执行所有带Profiling的C1编译；</li>
<li>第4层：可称为C2编译，也是将字节码编译为本地代码，但是会启用一些编译耗时较长的优化，甚至会根据性能监控信息进行一些不可靠的激进优化。</li>
</ul><p>在Java8中，默认开启分层编译，-client和-server的设置已经是无效的了。如果只想开启C2，可以关闭分层编译（-XX:-TieredCompilation），如果只想用C1，可以在打开分层编译的同时，使用参数：-XX:TieredStopAtLevel=1。</p><p>除了这种默认的混合编译模式，我们还可以使用“-Xint”参数强制虚拟机运行于只有解释器的编译模式下，这时JIT完全不介入工作；我们还可以使用参数“-Xcomp”强制虚拟机运行于只有JIT的编译模式下。</p><p>通过 java -version 命令行可以直接查看到当前系统使用的编译模式。如下图所示：</p><p><img src="https://static001.geekbang.org/resource/image/6e/11/6ea0938770cccc1b17c46f7b37d20711.jpg?wh=1222*650" alt=""></p><h3>热点探测</h3><p>在HotSpot虚拟机中的热点探测是JIT优化的条件，热点探测是基于计数器的热点探测，采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数，如果执行次数超过一定的阈值就认为它是“热点方法” 。</p><p>虚拟机为每个方法准备了两类计数器：方法调用计数器（Invocation Counter）和回边计数器（Back Edge Counter）。在确定虚拟机运行参数的前提下，这两个计数器都有一个确定的阈值，当计数器超过阈值溢出了，就会触发JIT编译。</p><p><strong>方法调用计数器：</strong>用于统计方法被调用的次数，方法调用计数器的默认阈值在C1模式下是1500次，在C2模式在是10000次，可通过-XX: CompileThreshold来设定；而在分层编译的情况下，-XX: CompileThreshold指定的阈值将失效，此时将会根据当前待编译的方法数以及编译线程数来动态调整。当方法计数器和回边计数器之和超过方法计数器阈值时，就会触发JIT编译器。</p><p><strong>回边计数器：</strong>用于统计一个方法中循环体代码执行的次数，在字节码中遇到控制流向后跳转的指令称为“回边”（Back Edge），该值用于计算是否触发C1编译的阈值，在不开启分层编译的情况下，C1默认为13995，C2默认为10700，可通过-XX: OnStackReplacePercentage=N来设置；而在分层编译的情况下，-XX: OnStackReplacePercentage指定的阈值同样会失效，此时将根据当前待编译的方法数以及编译线程数来动态调整。</p><p>建立回边计数器的主要目的是为了触发OSR（On StackReplacement）编译，即栈上编译。在一些循环周期比较长的代码段中，当循环达到回边计数器阈值时，JVM会认为这段是热点代码，JIT编译器就会将这段代码编译成机器语言并缓存，在该循环时间段内，会直接将执行代码替换，执行缓存的机器语言。</p><h3>编译优化技术</h3><p>JIT编译运用了一些经典的编译优化技术来实现代码的优化，即通过一些例行检查优化，可以智能地编译出运行时的最优性能代码。今天我们主要来学习以下两种优化手段：</p><p><strong>1.方法内联</strong></p><p>调用一个方法通常要经历压栈和出栈。调用方法是将程序执行顺序转移到存储该方法的内存地址，将方法的内容执行完后，再返回到执行该方法前的位置。</p><p>这种执行操作要求在执行前保护现场并记忆执行的地址，执行后要恢复现场，并按原来保存的地址继续执行。 因此，方法调用会产生一定的时间和空间方面的开销。</p><p>那么对于那些方法体代码不是很大，又频繁调用的方法来说，这个时间和空间的消耗会很大。<span class="orange">方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中，避免发生真实的方法调用。</span></p><p>例如以下方法：</p><pre><code>private int add1(int x1, int x2, int x3, int x4) {
    return add2(x1, x2) + add2(x3, x4);
}
private int add2(int x1, int x2) {
    return x1 + x2;
}
</code></pre><p>最终会被优化为：</p><pre><code>private int add1(int x1, int x2, int x3, int x4) {
    return x1 + x2+ x3 + x4;
}
</code></pre><p>JVM会自动识别热点方法，并对它们使用方法内联进行优化。我们可以通过-XX:CompileThreshold来设置热点方法的阈值。但要强调一点，热点方法不一定会被JVM做内联优化，如果这个方法体太大了，JVM将不执行内联操作。而方法体的大小阈值，我们也可以通过参数设置来优化：</p><ul>
<li>经常执行的方法，默认情况下，方法体大小小于325字节的都会进行内联，我们可以通过-XX:MaxFreqInlineSize=N来设置大小值；</li>
<li>不是经常执行的方法，默认情况下，方法大小小于35字节才会进行内联，我们也可以通过-XX:MaxInlineSize=N来重置大小值。</li>
</ul><p>之后我们就可以通过配置JVM参数来查看到方法被内联的情况：</p><pre><code>-XX:+PrintCompilation //在控制台打印编译过程信息
-XX:+UnlockDiagnosticVMOptions //解锁对JVM进行诊断的选项参数。默认是关闭的，开启后支持一些特定参数对JVM进行诊断
-XX:+PrintInlining //将内联方法打印出来
</code></pre><p>当我们设置VM参数：-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining之后，运行以下代码：</p><pre><code>	public static void main(String[] args) {
		for(int i=0; i&lt;1000000; i++) {//方法调用计数器的默认阈值在C1模式下是1500次，在C2模式在是10000次，我们循环遍历超过需要阈值
			add1(1,2,3,4);
		}
	}
</code></pre><p>我们可以看到运行结果中，显示了方法内联的日志：</p><p><img src="https://static001.geekbang.org/resource/image/ed/80/eda343938d9fa4ebb6b3331ae7b62680.jpg?wh=1612*212" alt=""></p><p>热点方法的优化可以有效提高系统性能，一般我们可以通过以下几种方式来提高方法内联：</p><ul>
<li>通过设置JVM参数来减小热点阈值或增加方法体阈值，以便更多的方法可以进行内联，但这种方法意味着需要占用更多地内存；</li>
<li>在编程中，避免在一个方法中写大量代码，习惯使用小方法体；</li>
<li>尽量使用final、private、static关键字修饰方法，编码方法因为继承，会需要额外的类型检查。</li>
</ul><p>2.逃逸分析</p><p>逃逸分析（Escape Analysis）是判断一个对象是否被外部方法引用或外部线程访问的分析技术，编译器会根据逃逸分析的结果对代码进行优化。</p><p><strong>栈上分配</strong></p><p>我们知道，在Java中默认创建一个对象是在堆中分配内存的，而当堆内存中的对象不再使用时，则需要通过垃圾回收机制回收，这个过程相对分配在栈中的对象的创建和销毁来说，更消耗时间和性能。这个时候，逃逸分析如果发现一个对象只在方法中使用，就会将对象分配在栈上。</p><p>以下是通过循环获取学生年龄的案例，方法中创建一个学生对象，我们现在通过案例来看看打开逃逸分析和关闭逃逸分析后，堆内存对象创建的数量对比。</p><pre><code>public static void main(String[] args) {
    for (int i = 0; i &lt; 200000 ; i++) {
    	getAge();
    }
}

public static int getAge(){
	Student person = new Student(&quot;小明&quot;,18,30);   
    return person.getAge();
}

static class Student {
    private String name;
    private int age;
   
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
</code></pre><p>然后，我们分别设置VM参数：Xmx1000m -Xms1000m -XX:-DoEscapeAnalysis -XX:+PrintGC以及 -Xmx1000m -Xms1000m -XX:+DoEscapeAnalysis -XX:+PrintGC，通过之前讲过的VisualVM工具，查看堆中创建的对象数量。</p><p>然而，运行结果却没有达到我们想要的优化效果，也许你怀疑是JDK版本的问题，然而我分别在1.6~1.8版本都测试过了，效果还是一样的：</p><p>（-server -Xmx1000m -Xms1000m -XX:-DoEscapeAnalysis -XX:+PrintGC）</p><p><img src="https://static001.geekbang.org/resource/image/83/a5/8313c788b71e4df16b29a98bb4b63ca5.jpg?wh=2368*1240" alt=""></p><p>（-server -Xmx1000m -Xms1000m -XX:+DoEscapeAnalysis -XX:+PrintGC）</p><p><img src="https://static001.geekbang.org/resource/image/25/63/259bd540cca1120813146cebbebef763.jpg?wh=2370*1254" alt=""></p><p><span class="orange">这其实是因为HotSpot虚拟机目前的实现导致栈上分配实现比较复杂，</span>可以说，在HotSpot中暂时没有实现这项优化。随着即时编译器的发展与逃逸分析技术的逐渐成熟，相信不久的将来HotSpot也会实现这项优化功能。</p><p><strong>  锁消除</strong></p><p>在非线程安全的情况下，尽量不要使用线程安全容器，比如StringBuffer。由于StringBuffer中的append方法被Synchronized关键字修饰，会使用到锁，从而导致性能下降。</p><p>但实际上，在以下代码测试中，StringBuffer和StringBuilder的性能基本没什么区别。这是因为在局部方法中创建的对象只能被当前线程访问，无法被其它线程访问，这个变量的读写肯定不会有竞争，这个时候JIT编译会对这个对象的方法锁进行锁消除。</p><pre><code>     public static String getString(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }
</code></pre><p><strong>标量替换</strong></p><p>逃逸分析证明一个对象不会被外部访问，如果这个对象可以被拆分的话，当程序真正执行的时候可能不创建这个对象，而直接创建它的成员变量来代替。将对象拆分后，可以分配对象的成员变量在栈或寄存器上，原本的对象就无需分配内存空间了。这种编译优化就叫做标量替换。</p><p>我们用以下代码验证：</p><pre><code>   public void foo() {
        TestInfo info = new TestInfo();
        info.id = 1;
        info.count = 99;
          ...//to do something
    }
</code></pre><p>逃逸分析后，代码会被优化为：</p><pre><code>   
   public void foo() {
        id = 1;
        count = 99;
        ...//to do something
    }
</code></pre><p>我们可以通过设置JVM参数来开关逃逸分析，还可以单独开关同步消除和标量替换，在JDK1.8中JVM是默认开启这些操作的。</p><pre><code>-XX:+DoEscapeAnalysis开启逃逸分析（jdk1.8默认开启，其它版本未测试）
-XX:-DoEscapeAnalysis 关闭逃逸分析

-XX:+EliminateLocks开启锁消除（jdk1.8默认开启，其它版本未测试）
-XX:-EliminateLocks 关闭锁消除

-XX:+EliminateAllocations开启标量替换（jdk1.8默认开启，其它版本未测试）
-XX:-EliminateAllocations 关闭就可以了
</code></pre><h2>总结</h2><p>今天我们主要了解了JKD1.8以及之前的类的编译和加载过程，Java源程序是通过Javac编译器编译成 .class文件，其中文件中包含的代码格式我们称之为Java字节码（bytecode）。</p><p>这种代码格式无法直接运行，但可以被不同平台JVM中的Interpreter解释执行。由于Interpreter的效率低下，JVM中的JIT会在运行时有选择性地将运行次数较多的方法编译成二进制代码，直接运行在底层硬件上。</p><p>在Java8之前，HotSpot集成了两个JIT，用C1和C2来完成JVM中的即时编译。虽然JIT优化了代码，但收集监控信息会消耗运行时的性能，且编译过程会占用程序的运行时间。</p><p>到了Java9，AOT编译器被引入。和JIT不同，AOT是在程序运行前进行的静态编译，这样就可以避免运行时的编译消耗和内存消耗，且 .class文件通过AOT编译器是可以编译成 .so的二进制文件的。</p><p>到了Java10，一个新的JIT编译器Graal被引入。Graal 是一个以 Java 为主要编程语言、面向 Java bytecode 的编译器。与用 C++  实现的 C1 和 C2 相比，它的模块化更加明显，也更容易维护。Graal 既可以作为动态编译器，在运行时编译热点方法；也可以作为静态编译器，实现 AOT 编译。</p><h2>思考题</h2><p><span class="orange">我们知道Class.forName和ClassLoader.loadClass都能加载类，你知道这两者在加载类时的区别吗？</span></p><p>期待在留言区看到你的见解。也欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他一起讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/1e/3a/5b21c01c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>nightmare</span>
  </div>
  <div class="_2_QraFYR_0">Class•forName有重载方法可以指定是否需要初始化，而默认的方法初始化设置为true这会初始化类执行链接和初始化操作，而ClasaLoader是有类加载器的loadClass方法加载，传入的是false只会执行链接操作，而不会执行初始化操作</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 回答非常详细</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-11 08:56:22</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/7b/57/a9b04544.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>QQ怪</span>
  </div>
  <div class="_2_QraFYR_0">看老师这篇分享花了1个小时，分享的干货很多，回答下老师的问题：<br>不同点：<br>1）Class.forName()除了将类的.class文件加载到jvm中之外，还会对类进行解释，执行类中的static块，还会执行给静态变量赋值的静态方法。<br>2）classLoader只干一件事情，就是将.class文件加载到jvm中，不会执行static中的内容,只有在newInstance才会去执行static块。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 这次内容确实有点多，慢慢消化。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-12 00:09:11</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/79/4b/740f91ca.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>-W.LI-</span>
  </div>
  <div class="_2_QraFYR_0">这其实是因为由于 HotSpot 虚拟机目前的实现导致栈上分配实现比较复杂。<br>栈上分配影响:<br>1.java只有值传递，跨方法的局部变量在栈上分配的话，在现有栈实现上会影响栈的回收。<br>2.栈属于线程所有，实现栈上分配，会消耗更多的内存。让java的多线程更吃内存。<br>3.如果实现栈上分配，还需要GC作用会弱化很多吧。<br>4.基类型栈上分配+引用类型堆分配-&gt;全栈上分配。这么实现的话hotspot感觉全推翻了。<br>老师好有理解的不对的请指出谢谢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: W.LI同学解释的很透彻，赞一个。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-13 17:42:00</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/d0/69/5dbdc245.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>张德</span>
  </div>
  <div class="_2_QraFYR_0">刘老师您好  听完这篇课程想起了以前去阿里面试一个面试官问到的问题  问题大概是 OSGI各个容器之间的类加载器是怎么进行隔离的  刘老师懂不懂  能不能回答一下  谢谢</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-20 17:35:49</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/f5/57/ce10fb1b.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>天天向上</span>
  </div>
  <div class="_2_QraFYR_0">文中讲到：逃逸分析如果发现一个对象只在方法中使用，就会将对象分配在栈上。然而我们的程序大多数对象都是只在方法中使用的啊，那栈就得需要很大的空间了啊？？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 使用方法时间非常短，所以在栈上的数据也就很快被释放掉了</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-12-29 23:21:46</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/09/08/f3547e77.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>lobby</span>
  </div>
  <div class="_2_QraFYR_0">看了另外一个专栏的jvm虚拟机的，再看编译这块没那么吃力了，这个文章有个总结性的作用挺好的</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-05-07 09:57:43</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/2b/fa/1cde88d4.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>大俊stan</span>
  </div>
  <div class="_2_QraFYR_0">老师花一章想把这些技术全部讲明白也是很难的</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-03-01 18:45:34</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/ce/4f/6afbb99a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>天信</span>
  </div>
  <div class="_2_QraFYR_0">很困惑线上服务的时候如何去调整这些参数达到最优解，比如调整 ComplieThrehold 大小或者其它参数，以及调整后到如何计算收益呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 一般情况下我们不会去调整这些参数，JVM默认值就已经可以满足大多数服务了，使用默认值即可。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-01-07 12:46:36</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/15/f4/0e/e200dd34.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>SDL</span>
  </div>
  <div class="_2_QraFYR_0">差了很多文章 还是没能找到在哪设置这些参数呢 望老师告知一下小白</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: springboot jar启动文件下，在启动时设置JVM参数就好了，java -xx（jvm参数）-jar xxx.jar</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-25 16:57:10</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/17/16/afff9a38.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>好孩子</span>
  </div>
  <div class="_2_QraFYR_0">为什么我测试的Class.forName和ClassLoader效果一样，都会执行静态代码块？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-08-29 16:27:54</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/b2/08/92f42622.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>尔冬橙</span>
  </div>
  <div class="_2_QraFYR_0">老师，Javap反编译的内容是生成class文件的中间过程么（在变成二进制文件前的汇编）</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: class文件是字节码，再JIT 或解释器会将字节码转换成机器码</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-03-04 21:30:20</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/17/f5/28/898a6947.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>deepz</span>
  </div>
  <div class="_2_QraFYR_0">干货满满 太干了！  给老师敬礼哈哈哈~ 慢慢消化希望都能成为自己的~</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-02-24 15:19:29</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/b3/c5/7fc124e2.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Liam</span>
  </div>
  <div class="_2_QraFYR_0">栈上分配这里，对局部变量对象的大小是否有要求，毕竟栈的内存比较小</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 目前hotspot虚拟机暂时不支持栈上分配对象。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-11 08:51:35</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/a2/ec/205fd50c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>我又不乱来</span>
  </div>
  <div class="_2_QraFYR_0">Class.forName加载类的时候会对类进行初始化，如静态代码块，ClassLoader 不会做初始化。spring做类加载的时候应该用的是ClassLoader把。超哥。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 正确</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-11 07:36:34</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/1f/80/ff/89ef5b24.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>吴 云</span>
  </div>
  <div class="_2_QraFYR_0">老师，回边计数器记录循环体的执行次数，如果循环体内有变量 随着循环次数发生改变 不确定变量值的情况下这种也能直接变成机器码么</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2023-08-20 09:25:41</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/06/a2/350c4af0.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>知易</span>
  </div>
  <div class="_2_QraFYR_0">牛啊牛啊，干货满满。来得晚了点，但是在补课的路上了</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-05-09 22:59:35</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/PiajxSqBRaELZPnUAiajaR5C25EDLWeJURggyiaOP5GGPe2qlwpQcm5e3ybib8OsP4tvddFDLVRSNNGL5I3SFPJHsA/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>null</span>
  </div>
  <div class="_2_QraFYR_0">javac 将 .java 文件编译成 .class 文件，即字节码文件。<br>interpreter 解释执行 .class 文件，JVM 层面的执行。<br>JIT 将 .class 文件编译为机器码，直接通过硬件执行。<br><br>是这样么，老师。谢谢</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-04-27 20:42:33</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/81/d4/e92abeb4.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Jecy-8</span>
  </div>
  <div class="_2_QraFYR_0">干货满满，谢谢老师</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-04-21 17:10:40</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/35/e9/9280de62.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>hiwon</span>
  </div>
  <div class="_2_QraFYR_0">你好，请问一下单例模式的double check变量一定要加volatile来确保指令重排问题？在类的创建到初始化是原子操作？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-01-17 23:09:32</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/86/4e/98dac64b.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Simba</span>
  </div>
  <div class="_2_QraFYR_0">老师，请教一下。逃逸分析那断代码如何用VisualVM抓到堆中创建的对象数量，我在执行的时候代码一瞬间就执行完了，VisualVM软件中也没有抓取到任何内存快照信息 ～  <br>我的开发环境是MAC系统，IDEA + visualvm launcher （插件）</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-01-06 15:13:19</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/22%20_%20%E6%B7%B1%E5%85%A5JVM%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91%E5%99%A8JIT%EF%BC%8C%E4%BC%98%E5%8C%96Java%E7%BC%96%E8%AF%91/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/21 _ 磨刀不误砍柴工：欲知JVM调优先了解JVM内存模型" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/21%20_%20%E7%A3%A8%E5%88%80%E4%B8%8D%E8%AF%AF%E7%A0%8D%E6%9F%B4%E5%B7%A5%EF%BC%9A%E6%AC%B2%E7%9F%A5JVM%E8%B0%83%E4%BC%98%E5%85%88%E4%BA%86%E8%A7%A3JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/">极客时间/Java性能调优实战/21 _ 磨刀不误砍柴工：欲知JVM调优先了解JVM内存模型</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/21%20_%20%E7%A3%A8%E5%88%80%E4%B8%8D%E8%AF%AF%E7%A0%8D%E6%9F%B4%E5%B7%A5%EF%BC%9A%E6%AC%B2%E7%9F%A5JVM%E8%B0%83%E4%BC%98%E5%85%88%E4%BA%86%E8%A7%A3JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:19.465Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="21 _ 磨刀不误砍柴工：欲知JVM调优先了解JVM内存模型" src="https://static001.geekbang.org/resource/audio/ba/b8/ba587aeb95138d9bba29bf06b2d42fb8.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>从今天开始，我将和你一起探讨Java虚拟机（JVM）的性能调优。JVM算是面试中的高频问题了，通常情况下总会有人问到：请你讲解下JVM的内存模型，JVM的性能调优做过吗？</p><h2>为什么JVM在Java中如此重要？</h2><p>首先你应该知道，运行一个Java应用程序，我们必须要先安装JDK或者JRE包。这是因为Java应用在编译后会变成字节码，然后通过字节码运行在JVM中，而JVM是JRE的核心组成部分。</p><p><span class="orange">JVM不仅承担了Java字节码的分析（JIT compiler）和执行（Runtime），同时也内置了自动内存分配管理机制。</span>这个机制可以大大降低手动分配回收机制可能带来的内存泄露和内存溢出风险，使Java开发人员不需要关注每个对象的内存分配以及回收，从而更专注于业务本身。</p><h2>从了解内存模型开始</h2><p>JVM自动内存分配管理机制的好处很多，但实则是把双刃剑。这个机制在提升Java开发效率的同时，也容易使Java开发人员过度依赖于自动化，弱化对内存的管理能力，这样系统就很容易发生JVM的堆内存异常，垃圾回收（GC）的方式不合适以及GC次数过于频繁等问题，这些都将直接影响到应用服务的性能。</p><p>因此，要进行JVM层面的调优，就需要深入了解JVM内存分配和回收原理，这样在遇到问题时，我们才能通过日志分析快速地定位问题；也能在系统遇到性能瓶颈时，通过分析JVM调优来优化系统性能。这也是整个模块四的重点内容，今天我们就从JVM的内存模型学起，为后续的学习打下一个坚实的基础。</p><!-- [[[read_end]]] --><h2>JVM内存模型的具体设计</h2><p>我们先通过一张JVM内存模型图，来熟悉下其具体设计。<span class="orange">在Java中，JVM内存模型主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈。</span></p><p><img src="https://static001.geekbang.org/resource/image/df/8b/dfd02c98d495c4c4ed201ea7fe0e3f8b.jpg?wh=1022*664" alt=""></p><p>JVM的5个分区具体是怎么实现的呢？我们一一分析。</p><h3>1. 堆（Heap）</h3><p>堆是JVM内存中最大的一块内存空间，该内存被所有线程共享，几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代，新生代又被进一步划分为Eden和Survivor区，最后Survivor由From Survivor和To Survivor组成。</p><p>在Java6版本中，永久代在非堆内存区；到了Java7版本，永久代的静态变量和运行时常量池被合并到了堆中；而到了Java8，永久代被元空间取代了。 结构如下图所示：</p><p><img src="https://static001.geekbang.org/resource/image/99/6c/9906824978c891c86524f9394102de6c.png?wh=619*674" alt=""></p><h3>2. 程序计数器（Program Counter Register）</h3><p>程序计数器是一块很小的内存空间，主要用来记录各个线程执行的字节码的地址，例如，分支、循环、跳转、异常、线程恢复等都依赖于计数器。</p><p>由于Java是多线程语言，当执行的线程数量超过CPU核数时，线程之间会根据时间片轮询争夺CPU资源。如果一个线程的时间片用完了，或者是其它原因导致这个线程的CPU资源被提前抢夺，那么这个退出的线程就需要单独的一个程序计数器，来记录下一条运行的指令。</p><h3>3. 方法区（Method Area）</h3><p>很多开发者都习惯将方法区称为“永久代”，其实这两者并不是等价的。</p><p>HotSpot虚拟机使用永久代来实现方法区，但在其它虚拟机中，例如，Oracle的JRockit、IBM的J9就不存在永久代一说。因此，方法区只是JVM中规范的一部分，可以说，在HotSpot虚拟机中，设计人员使用了永久代来实现了JVM规范的方法区。</p><p><span class="orange">方法区主要是用来存放已被虚拟机加载的类相关信息，</span>包括类信息、运行时常量池、字符串常量池。类信息又包括了类的版本、字段、方法、接口和父类等信息。</p><p>JVM在执行某个类的时候，必须经过加载、连接、初始化，而连接又包括验证、准备、解析三个阶段。在加载类的时候，JVM会先加载class文件，而在class文件中除了有类的版本、字段、方法和接口等描述信息外，还有一项信息是常量池(Constant Pool Table)，用于存放编译期间生成的各种字面量和符号引用。</p><p>字面量包括字符串（String a=“b”）、基本类型的常量（final修饰的变量），符号引用则包括类和方法的全限定名（例如String这个类，它的全限定名就是Java/lang/String）、字段的名称和描述符以及方法的名称和描述符。</p><p>而当类加载到内存中后，JVM就会将class文件常量池中的内容存放到运行时的常量池中；在解析阶段，JVM会把符号引用替换为直接引用（对象的索引值）。</p><p>例如，类中的一个字符串常量在class文件中时，存放在class文件常量池中的；在JVM加载完类之后，JVM会将这个字符串常量放到运行时常量池中，并在解析阶段，指定该字符串对象的索引值。运行时常量池是全局共享的，多个类共用一个运行时常量池，class文件中常量池多个相同的字符串在运行时常量池只会存在一份。</p><p><span class="orange">方法区与堆空间类似，也是一个共享内存区，所以方法区是线程共享的。</span>假如两个线程都试图访问方法区中的同一个类信息，而这个类还没有装入JVM，那么此时就只允许一个线程去加载它，另一个线程必须等待。</p><p>在HotSpot虚拟机、Java7版本中已经将永久代的静态变量和运行时常量池转移到了堆中，其余部分则存储在JVM的非堆内存中，而Java8版本已经将方法区中实现的永久代去掉了，并用元空间（class metadata）代替了之前的永久代，并且元空间的存储位置是本地内存。之前永久代的类的元数据存储在了元空间，永久代的静态变量（class static variables）以及运行时常量池（runtime constant pool）则跟Java7一样，转移到了堆中。</p><p><strong>那你可能又有疑问了，Java8为什么使用元空间替代永久代，这样做有什么好处呢？</strong></p><p>官方给出的解释是：</p><ul>
<li>移除永久代是为了融合 HotSpot JVM 与 JRockit VM 而做出的努力，因为JRockit没有永久代，所以不需要配置永久代。</li>
<li>永久代内存经常不够用或发生内存溢出，爆出异常java.lang.OutOfMemoryError: PermGen。这是因为在JDK1.7版本中，指定的PermGen区大小为8M，由于PermGen中类的元数据信息在每次FullGC的时候都可能被收集，回收率都偏低，成绩很难令人满意；还有，为PermGen分配多大的空间很难确定，PermSize的大小依赖于很多因素，比如，JVM加载的class总数、常量池的大小和方法的大小等。</li>
</ul><h3>4.虚拟机栈（VM stack）</h3><p>Java虚拟机栈是线程私有的内存空间，它和Java线程一起创建。当创建一个线程时，会在虚拟机栈中申请一个线程栈，用来保存方法的局部变量、操作数栈、动态链接方法和返回地址等信息，并参与方法的调用和返回。每一个方法的调用都伴随着栈帧的入栈操作，方法的返回则是栈帧的出栈操作。</p><h3>5.本地方法栈（Native Method Stack）</h3><p>本地方法栈跟Java虚拟机栈的功能类似，Java虚拟机栈用于管理Java函数的调用，而本地方法栈则用于管理本地方法的调用。但本地方法并不是用Java实现的，而是由C语言实现的。</p><h2>JVM的运行原理</h2><p>看到这里，相信你对JVM内存模型已经有个充分的了解了。接下来，我们通过一个案例来了解下代码和对象是如何分配存储的，Java代码又是如何在JVM中运行的。</p><pre><code>public class JVMCase {

	// 常量
	public final static String MAN_SEX_TYPE = &quot;man&quot;;

	// 静态变量
	public static String WOMAN_SEX_TYPE = &quot;woman&quot;;

	public static void main(String[] args) {
		
		Student stu = new Student();
		stu.setName(&quot;nick&quot;);
		stu.setSexType(MAN_SEX_TYPE);
		stu.setAge(20);
		
		JVMCase jvmcase = new JVMCase();
		
		// 调用静态方法
		print(stu);
		// 调用非静态方法
		jvmcase.sayHello(stu);
	}


	// 常规静态方法
	public static void print(Student stu) {
		System.out.println(&quot;name: &quot; + stu.getName() + &quot;; sex:&quot; + stu.getSexType() + &quot;; age:&quot; + stu.getAge()); 
	}


	// 非静态方法
	public void sayHello(Student stu) {
		System.out.println(stu.getName() + &quot;say: hello&quot;); 
	}
}

class Student{
	String name;
	String sexType;
	int age;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	public String getSexType() {
		return sexType;
	}
	public void setSexType(String sexType) {
		this.sexType = sexType;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}
</code></pre><p><strong>当我们通过Java运行以上代码时，JVM的整个处理过程如下：</strong></p><p>1.JVM向操作系统申请内存，JVM第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间，根据内存大小找到具体的内存分配表，然后把内存段的起始地址和终止地址分配给JVM，接下来JVM就进行内部分配。</p><p>2.JVM获得内存空间后，会根据配置参数分配堆、栈以及方法区的内存大小。</p><p>3.class文件加载、验证、准备以及解析，其中准备阶段会为类的静态变量分配内存，初始化为系统的初始值（这部分我在第21讲还会详细介绍）。</p><p><img src="https://static001.geekbang.org/resource/image/94/32/94e6ebbaa0a23d677a4ad752e3e68732.jpg?wh=1258*930" alt=""></p><p>4.完成上一个步骤后，将会进行最后一个初始化阶段。在这个阶段中，JVM首先会执行构造器&lt;clinit&gt;方法，编译器会在.java 文件被编译成.class 文件时，收集所有类的初始化代码，包括静态变量赋值语句、静态代码块、静态方法，收集在一起成为 &lt;clinit&gt;() 方法。</p><p><img src="https://static001.geekbang.org/resource/image/29/59/29d54f4a8e1ecf388adc6b99cd5e0159.jpg?wh=1248*864" alt=""></p><p>5.执行方法。启动main线程，执行main方法，开始执行第一行代码。此时堆内存中会创建一个student对象，对象引用student就存放在栈中。</p><p><img src="https://static001.geekbang.org/resource/image/c6/7e/c6702aea3f1aaca60b1cd2e38981ad7e.jpg?wh=1372*968" alt=""></p><p>6.此时再次创建一个JVMCase对象，调用sayHello非静态方法，sayHello方法属于对象JVMCase，此时sayHello方法入栈，并通过栈中的student引用调用堆中的Student对象；之后，调用静态方法print，print静态方法属于JVMCase类，是从静态方法中获取，之后放入到栈中，也是通过student引用调用堆中的student对象。</p><p><img src="https://static001.geekbang.org/resource/image/b7/23/b7d00191a1d42def9633b8ea8491cf23.jpg?wh=1414*984" alt=""></p><p>了解完实际代码在JVM中分配的内存空间以及运行原理，相信你会更加清楚内存模型中各个区域的职责分工。</p><h2>总结</h2><p>这讲我们主要深入学习了最基础的内存模型设计，了解其各个分区的作用及实现原理。</p><p>如今，JVM在很大程度上减轻了Java开发人员投入到对象生命周期的管理精力。在使用对象的时候，JVM会自动分配内存给对象，在不使用的时候，垃圾回收器会自动回收对象，释放占用的内存。</p><p>但在某些情况下，正常的生命周期不是最优的选择，有些对象按照JVM默认的方式，创建成本会很高。比如，我在<a href="https://time.geekbang.org/column/article/97215" target="_blank" rel="noopener">第03讲</a>讲到的String对象，在特定的场景使用String.intern可以很大程度地节约内存成本。<span class="orange">我们可以使用不同的引用类型，改变一个对象的正常生命周期，从而提高JVM的回收效率，这也是JVM性能调优的一种方式。</span></p><h2>思考题</h2><p>这讲我只提到了堆内存中对象分配内存空间的过程，那如果有一个类中定义了String a="b"和String c = new String(“b”)，请问这两个对象会分别创建在JVM内存模型中的哪块区域呢？</p><p>期待在留言区看到你的答案。也欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他一起讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83eotSSnZic41tGkbflx0ogIg3ia6g2muFY1hCgosL2t3icZm7I8Ax1hcv1jNgr6vrZ53dpBuGhaoc6DKg/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>张学磊</span>
  </div>
  <div class="_2_QraFYR_0">String a=&quot;b&quot;可能创建一个对象或者不创建对象,如果&quot;b&quot;这个字符串在常量池里不存在会在常量池创建一个String对象&quot;b&quot;,如果已经存在则a直接reference to这个常量池里的对象;<br>String c= new String(&quot;b&quot;)至少创建一个对象,也可能两个,因为用到new关键字,会在堆内在创建一个的String对象,它的值是&quot;b&quot;。同时,如果&quot;b&quot;这个字符串在常量池里不存在,会在常量池创建这个一个String对象&quot;b&quot;。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-09 14:00:40</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/fe/4c/46eb517a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Xiao</span>
  </div>
  <div class="_2_QraFYR_0">老师，这儿其实应该说JVM内存结构更合适！JVM内存模型是一种规范，和JVM内存结构不是一个概念。其次，元空间，在Java8，不是在堆内分配的，它的大小是依赖于本地内存大小！</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 感谢Xiao同学的提醒。<br><br>我想你说的内存模型应该是指Java内存模型（JMM）吧。这里的JVM内存模型跟Java内存模型是不一样的，这里的JVM内存模型和和内存结构是一个意思。<br><br>元空间是分配的本地内存，文中开始描述不清楚（已纠正），但后面有明确说明。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-09 09:31:39</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/b3/c5/7fc124e2.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Liam</span>
  </div>
  <div class="_2_QraFYR_0">请教一个问题，所以1.8开始，方法区是堆的一部分吗？也即是说，方法区的大小受限于堆</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 方法区不是堆的一部分，方法区和堆存在交集。方法区的静态变量和运行时常量池存放在堆中，但类的元信息等还是存放在了本地内存中。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-09 08:08:58</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/dc/53/ccb62ea0.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>夏天39度</span>
  </div>
  <div class="_2_QraFYR_0">超哥，我可以这样理解吗，方法区只是一个逻辑概念，方法区是包括元空间物理内存和堆内存</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-31 10:18:11</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/16/04/71/853b2292.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Gred</span>
  </div>
  <div class="_2_QraFYR_0">老师，运行时变量应该都在方法区中，从java7开始只有字符串常量池移到堆中而已</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 严格来说，是静态常量池和运行时常量池，静态常量池是存放字符串字面量、符号引用以及类和方法的信息，而运行时常量池存放的是运行时一些直接引用。<br><br>运行时常量池是在类加载完成之后，将静态常量池中的符号引用值转存到运行时常量池中，类在解析之后，将符号引用替换成直接引用。<br><br>这两个常量池在JDK1.7版本之后，就移到堆内存中了，这里指的是物理空间，而逻辑上还是属于方法区（方法区是逻辑分区）。<br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-02 00:04:52</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/a2/ec/205fd50c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>我又不乱来</span>
  </div>
  <div class="_2_QraFYR_0">String a=&quot;b&quot;应该会放在字符串常量池中。<br>String c= new String(&quot;b&quot;) 首先应该放在 堆中一份，再在常量池中放一份。但是常量池中有b了。<br>第一次留言。不知道理解的对不对。超哥</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 正确</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-09 07:42:01</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/36/d2/c7357723.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>发条橙子 。</span>
  </div>
  <div class="_2_QraFYR_0">老师，这句话怎么理解 <br><br>之前永久代的类的元数据存储在了元空间，永久代的静态变量（class static variables）以及运行时常量池（runtime constant pool）则跟 Java7 一样，转移到了堆中。<br><br>方法区的一部分是由永久代实现的，永久代主要存储类的静态数据以及运行时常量池并储存在堆内存中。 但是由于容易发生permen内存溢出，后来就发明了元数据空间。那我理解元空间除了存储之前方法区的类信息还包括之前放在永久代中的 静态变量 和 运行时常量池 。 <br>文中为什么说和jdk7一样还是转移到堆中，那不是没有变化么？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的，没有变化。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-20 18:20:47</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/b2/08/92f42622.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>尔冬橙</span>
  </div>
  <div class="_2_QraFYR_0">老师，我看有人说字符串常量池只放引用；那new出来除了堆中会有一个对象，如果字符串常量池没有，也会创建一个，这个对象是在堆中非字符串常量池的地方么</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 我们说的常量池一般分为静态常量池和运行时常量池，通常字符串常量是存放的引用是在运行时常量池，而字面量是存在了静态常量池。动态生成的字符串，对象是存放在堆中，如果调用intern方法，会将引用存放在常量池中。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-02 18:14:21</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/f3/32/44297a74.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>黑夜里的猫</span>
  </div>
  <div class="_2_QraFYR_0">字符串常量不是在java8中已经被放入到堆中了吗，应该不在方法区中了，但是看到老师的图中还在方法区中</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 方法区是一个规范，并不是一个物理空间，我们这里说的字符串常量放在堆内存空间中，是指实际的物理空间。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-09 19:50:20</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/14/ac/62/37912d51.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>东方奇骥</span>
  </div>
  <div class="_2_QraFYR_0">老师，问一下，1.8静态变量和常量存储在的堆里面，那元空间里是什么？文中说之前永久带类的数据存储在了元空间，不是很理解，</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 元空间主要存储类的一些信息，包括方法、字段、类等描述类信息。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-12 12:54:46</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/15/ff/0a/12faa44e.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>晓杰</span>
  </div>
  <div class="_2_QraFYR_0">创建一个线程，就会在虚拟机中申请一个栈帧，这句话有问题吧<br>应该是创建一个线程，会创建一个栈，然后方法调用一次，就会申请一个栈帧吧</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的，这里是申请一个线程栈。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-11 15:48:04</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/59/66/39eeb3f9.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Cain</span>
  </div>
  <div class="_2_QraFYR_0">常量池在哪个区？堆区？栈区？方法区？静态区？方法区，静态区他俩是什么关系？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 在逻辑空间是属于方法区。堆、栈、方法区等，这些是一种规范，是逻辑上的分区。<br><br>在物理空间中，常量池是存储在堆内存空间的。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-10 19:30:35</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/ac/ef/494f56c3.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>crazypokerk</span>
  </div>
  <div class="_2_QraFYR_0">引用a和c都会放在栈中，但是a直接指向堆中的运行时常量池中的&quot;b&quot;，而引用c会先在堆中创建一个String对象，该对象会指向运行时常量池中的&quot;b&quot;。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 厉害</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-09 09:48:09</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/16/02/85/9a81a973.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>ZHANG</span>
  </div>
  <div class="_2_QraFYR_0">老师，是这样吗，java8中类的静态变量，运行时常量池，字符串常量池都在堆中，那元空间只有一些类的信息了，比如版本什么的。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-10-31 20:51:30</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/b2/b3/798a4bb2.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>帽子丨影</span>
  </div>
  <div class="_2_QraFYR_0">元空间的存储位置时本地内存，请问下本地内存是个什么东西，在第一张图里没找到啊。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 本地内存是一种非JVM堆内存</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-20 18:58:32</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/49/85/3f161d95.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Alpha</span>
  </div>
  <div class="_2_QraFYR_0">而到了 Java8，静态变量和运行时常量池与 Java7 的永久代一样，都移到了堆中。 <br><br>这句没看懂。。上一句说到java7把永久代里的静态变量和运行时常量池移到堆中，这一句又说java8移了 静态变量和运行时常量池？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 已捋顺，也就是说静态变量和运行时常量池依然存储在堆内存物理空间中。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-09 21:40:02</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/e5/10/0a94311f.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>joker</span>
  </div>
  <div class="_2_QraFYR_0">超哥，您好，不知道您还能不能看到留言并回复。<br>在学习JVM的内存模型的时候，我有这样一些疑惑：<br>1、我们通常只是定义了堆大小(-Xms初始，-Xmx最大)，虚拟机栈大小(-Xss)。<br>但是我发现这并不能计算出一个java进程占用的全部内存大小。<br>以下是我自己理解的(JDK1.8)：<br>java进程占用的内存<br>    =JVM管理的内存+非JVM管理的内存<br>    =线程独立的内存+线程共享的内存<br>    =n*(虚拟机栈内存+程序计数器内存+本地方法栈内存)+堆内存(heap)+非堆内存(non-heap)+元空间(metaspace)+堆外内存(off-heap:direct memory)<br>其中：<br>    JVM管理的内存：n*(虚拟机栈内存+程序计数器内存+本地方法栈内存)+堆内存(heap)+非堆内存(non-heap)<br>    非JVM管理的内存：元空间(metaspace)+堆外内存(off-heap:direct memory)<br>    线程独立的内存：n*(虚拟机栈内存+程序计数器内存+本地方法栈内存)，n是线程数<br>    线程共享的内存：堆内存(heap)+非堆内存(non-heap)+元空间(metaspace)+对外内存(off-heap:direct memory)<br>以上我的理解是否正确呢？麻烦超哥指点<br><br><br>2、在JDK1.7及以前，有个永久代(PermGen)，也就是文中说的方法区。这块区域也被称为非堆内存<br>    那么在JDK1.8及以后，永久代变成了元空间，到了JVM管理之外了，那么JDK1.8及以后的版本中还有非堆内存(non-heap)的说法吗？如果有的话，是指什么呢？<br><br>3、关于线程独立的这块内存{n*(虚拟机栈内存+程序计数器内存+本地方法栈内存)，n是线程数}，它是完全独立于其他的内存的吗？<br>    还是会分享堆内存，受到堆内存大小的限制<br>    还是说Thread对象是建立在堆内存，然后每个Thread对应的虚拟机栈都是独立的吗？ 换句话说，随着Thread的增加(堆内存充足：还能给新的对象分配内存)，java进程占用的内存会越来越大-----我觉得这肯定不对，但是我却无法解释<br><br>4、我做了一些测试(JDK1.8)：<br>    4.1、指定很小的堆内存，改变虚拟机栈大小<br>        4.1.1、-Xms2m -Xmx2m -Xss16m 启动java进程，直到递归调用1,016,085深度，会报StackOverflowError<br>        4.1.2、-Xms2m -Xmx2m -Xss8m 启动java进程，直到递归调用318,031深度，会报StackOverflowError<br>    4.2、指定很小的堆内存，如-Xms2m -Xmx2m，最终会报OutOfMemoryError</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-06-05 14:15:32</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/WtA4zsJVEhatPFnoAgMoiaguBwsiacRsibsdKuHQ7FnO17EUOx7hcEfM6XAt2wmbCiaAN0LSUPSO98icMSmfPpxg1LQ/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>knightyxgy</span>
  </div>
  <div class="_2_QraFYR_0">请教老师，看了您对其他留言的回答给予了肯定：1、JVM的堆和操作系统的堆不是一个概念。2、静态常量池和运行时常量池移入的堆内存是指物理内存，逻辑上还是属于JVM对于方法区的规范。3、方法区实际上是在本地内存即堆外内存分配的<br>根据老师的回答理解下来，两个常量池移入的堆内存就是操作系统层次的概念，可是JVM内存无论是堆内存还是非堆内存（元空间）应该都是操作系统抽象的堆内存中分配的吧。如何理解操作系统层次的堆和JVM的堆以及本地内存之间的关系呢？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-05-03 22:35:59</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/16/56/c6/0b449bc6.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>斐波那契</span>
  </div>
  <div class="_2_QraFYR_0">老师 看完这个 对于运行时常量池和字符串常量池有点搞不清 是不是运行时常量池包括字符串常量池还是说这两个是不同的东西?</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 两者有区别，通常方法区中有静态常量池和运行时常量池，静态常量池主要存储的是字面量以及符号引用等信息，而运行时常量池存储的是类运行加载时生成的直接引用等信息。静态常量池也包括了我们说的字符串常量池。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-11-23 22:25:06</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/21/b3/db933462.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>文灏</span>
  </div>
  <div class="_2_QraFYR_0">请教一下，1.8中类的元数据是放在元数据区还是方法区呢？看得有点晕</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 元空间是属于方法区的，方法区只是一个逻辑分区，而元空间是具体实现。所以类的元数据是存放在元空间的，逻辑上属于方法区。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-15 21:24:15</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/21%20_%20%E7%A3%A8%E5%88%80%E4%B8%8D%E8%AF%AF%E7%A0%8D%E6%9F%B4%E5%B7%A5%EF%BC%9A%E6%AC%B2%E7%9F%A5JVM%E8%B0%83%E4%BC%98%E5%85%88%E4%BA%86%E8%A7%A3JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/加餐 _ 什么是数据的强、弱一致性？" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/%E5%8A%A0%E9%A4%90%20_%20%E4%BB%80%E4%B9%88%E6%98%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%BC%BA%E3%80%81%E5%BC%B1%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%9F/">极客时间/Java性能调优实战/加餐 _ 什么是数据的强、弱一致性？</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/%E5%8A%A0%E9%A4%90%20_%20%E4%BB%80%E4%B9%88%E6%98%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%BC%BA%E3%80%81%E5%BC%B1%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%9F/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:13.585Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="加餐 _ 什么是数据的强、弱一致性？" src="https://static001.geekbang.org/resource/audio/81/9c/81dbba7aac97cb60b0779ef63d5f069c.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>在<a href="https://time.geekbang.org/column/article/103541" target="_blank" rel="noopener">第17讲</a>讲解并发容器的时候，我提到了“强一致性”和“弱一致性”。很多同学留言表示对这个概念没有了解或者比较模糊，今天这讲加餐就来详解一下。</p><p>说到一致性，其实在系统的很多地方都存在数据一致性的相关问题。除了在并发编程中保证共享变量数据的一致性之外，还有数据库的ACID中的C（Consistency 一致性）、分布式系统的CAP理论中的C（Consistency 一致性）。下面<span class="orange">我们主要讨论的就是“并发编程中共享变量的一致性”。</span></p><p>在并发编程中，Java是通过共享内存来实现共享变量操作的，所以在多线程编程中就会涉及到数据一致性的问题。</p><p>我先通过一个经典的案例来说明下多线程操作共享变量可能出现的问题，假设我们有两个线程（线程1和线程2）分别执行下面的方法，x是共享变量：</p><pre><code>//代码1
public class Example {
    int x = 0;
    public void count() {
        x++;                     //1
        System.out.println(x)//2
    }
}
</code></pre><p><img src="https://static001.geekbang.org/resource/image/e1/33/e1dfb18f71c76d1468fa94d43f8ca933.jpg?wh=1058*130" alt=""></p><p>如果两个线程同时运行，两个线程的变量的值可能会出现以下三种结果：</p><p><img src="https://static001.geekbang.org/resource/image/fb/9b/fb45f0c91af1a063d1f2db28dd21c49b.jpg?wh=1066*134" alt=""></p><h2>Java存储模型</h2><p>2,1和1,2的结果我们很好理解，那为什么会出现以上1,1的结果呢？</p><p>我们知道，Java采用共享内存模型来实现多线程之间的信息交换和数据同步。在解释为什么会出现这样的结果之前，我们先通过下图来简单了解下Java的内存模型（第21讲还会详解），程序在运行时，局部变量将会存放在虚拟机栈中，而共享变量将会被保存在堆内存中。</p><!-- [[[read_end]]] --><p><img src="https://static001.geekbang.org/resource/image/df/8b/dfd02c98d495c4c4ed201ea7fe0e3f8b.jpg?wh=1022*664" alt=""></p><p>由于局部变量是跟随线程的创建而创建，线程的销毁而销毁，所以存放在栈中，由上图我们可知，Java栈数据不是所有线程共享的，所以不需要关心其数据的一致性。</p><p>共享变量存储在堆内存或方法区中，由上图可知，堆内存和方法区的数据是线程共享的。而堆内存中的共享变量在被不同线程操作时，会被加载到自己的工作内存中，也就是CPU中的高速缓存。</p><p>CPU 缓存可以分为一级缓存（L1）、二级缓存（L2）和三级缓存（L3），每一级缓存中所储存的全部数据都是下一级缓存的一部分。当 CPU 要读取一个缓存数据时，首先会从一级缓存中查找；如果没有找到，再从二级缓存中查找；如果还是没有找到，就从三级缓存或内存中查找。</p><p>如果是单核CPU运行多线程，多个线程同时访问进程中的共享数据，CPU 将共享变量加载到高速缓存后，不同线程在访问缓存数据的时候，都会映射到相同的缓存位置，这样即使发生线程的切换，缓存仍然不会失效。</p><p>如果是多核CPU运行多线程，每个核都有一个 L1缓存，如果多个线程运行在不同的内核上访问共享变量时，每个内核的L1缓存将会缓存一份共享变量。</p><p>假设线程A操作CPU从堆内存中获取一个缓存数据，此时堆内存中的缓存数据值为0，该缓存数据会被加载到L1缓存中，在操作后，缓存数据的值变为1，然后刷新到堆内存中。</p><p>在正好刷新到堆内存中之前，又有另外一个线程B将堆内存中为0的缓存数据加载到了另外一个内核的L1缓存中，此时线程A将堆内存中的数据刷新到了1，而线程B实际拿到的缓存数据的值为0。</p><p>此时，内核缓存中的数据和堆内存中的数据就不一致了，且线程B在刷新缓存到堆内存中的时候也将覆盖线程A中修改的数据。这时就产生了数据不一致的问题。</p><p><img src="https://static001.geekbang.org/resource/image/38/05/3835ed3c7df4f859cc77de5d829dab05.jpg?wh=1232*766" alt=""></p><p>了解完内存模型之后，结合以上解释，我们就可以回过头来看看第一段代码中的运行结果是如何产生的了。看到这里，相信你可以理解图中1,1的运行结果了。</p><p><img src="https://static001.geekbang.org/resource/image/dc/2e/dcaf4e9441871ca17f9b263cbd2b082e.jpg?wh=1028*922" alt=""></p><h2>重排序</h2><p>除此之外，在Java内存模型中，还存在重排序的问题。请看以下代码：</p><pre><code>//代码1
public class Example {
    int x = 0;
    boolean flag = false;
    public void writer() {
        x = 1;                //1
        flag = true;          //2
    }

    public void reader() {
        if (flag) {           //3
             int r1 = x;      //4
             System.out.println(r1==x)
        }
    }
}
</code></pre><p><img src="https://static001.geekbang.org/resource/image/ae/a8/ae1dc00bfc5e3a751cc427841d14c9a8.jpg?wh=896*192" alt=""></p><p>如果两个线程同时运行，线程2中的变量的值可能会出现以下两种可能：</p><p><img src="https://static001.geekbang.org/resource/image/ca/d6/ca6a5d4bb77ff67b1d30fcaac37c25d6.jpg?wh=898*130" alt=""></p><p>现在一起来看看 r1=1  的运行结果，如下图所示：</p><p><img src="https://static001.geekbang.org/resource/image/0c/33/0c1e6c9a2951b1ba87b32be15708f633.jpg?wh=914*906" alt=""></p><p>那r1=0又是怎么获取的呢？我们再来看一个时序图：</p><p><img src="https://static001.geekbang.org/resource/image/88/17/880cbe050a2f65b1d9b457588f64f117.jpg?wh=966*904" alt=""></p><p><span class="orange">在不影响运算结果的前提下，编译器有可能会改变顺序代码的指令执行顺序，特别是在一些可以优化的场景。</span></p><p>例如，在以下案例中，编译器为了尽可能地减少寄存器的读取、存储次数，会充分复用寄存器的存储值。如果没有进行重排序优化，正常的执行顺序是步骤1\2\3，而在编译期间进行了重排序优化之后，执行的步骤有可能就变成了步骤1/3/2或者2/1/3，这样就能减少一次寄存器的存取次数。</p><pre><code>int x = 1;//步骤1：加载x变量的内存地址到寄存器中，加载1到寄存器中，CPU通过mov指令把1写入到寄存器指定的内存中
boolean flag = true; //步骤2 加载flag变量的内存地址到寄存器中，加载true到寄存器中，CPU通过mov指令把1写入到寄存器指定的内存中
int y = x + 1;//步骤3 重新加载x变量的内存地址到寄存器中，加载1到寄存器中，CPU通过mov指令把1写入到寄存器指定的内存中
</code></pre><p>在 JVM 中，重排序是十分重要的一环，特别是在并发编程中。可 JVM 要是能对它们进行任意排序的话，也可能会给并发编程带来一系列的问题，其中就包括了一致性的问题。</p><h2>Happens-before规则</h2><p>为了解决这个问题，Java提出了Happens-before规则来规范线程的执行顺序：</p><ul>
<li>程序次序规则：在单线程中，代码的执行是有序的，虽然可能会存在运行指令的重排序，但最终执行的结果和顺序执行的结果是一致的；</li>
<li>锁定规则：一个锁处于被一个线程锁定占用状态，那么只有当这个线程释放锁之后，其它线程才能再次获取锁操作；</li>
<li>volatile变量规则：如果一个线程正在写volatile变量，其它线程读取该变量会发生在写入之后；</li>
<li>线程启动规则：Thread对象的start()方法先行发生于此线程的其它每一个动作；</li>
<li>线程终结规则：线程中的所有操作都先行发生于对此线程的终止检测；</li>
<li>对象终结规则：一个对象的初始化完成先行发生于它的finalize()方法的开始；</li>
<li>传递性：如果操作A happens-before 操作B，操作B happens-before操作C，那么操作A happens-before 操作C；</li>
<li>线程中断规则：对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。</li>
</ul><p><strong>结合这些规则，我们可以将一致性分为以下几个级别：</strong></p><p>严格一致性（强一致性）：所有的读写操作都按照全局时钟下的顺序执行，且任何时刻线程读取到的缓存数据都是一样的，Hashtable就是严格一致性；</p><p><img src="https://static001.geekbang.org/resource/image/65/6b/650c9490bad5962cfcdd4bedf3e41f6b.jpg?wh=1644*454" alt=""></p><p>顺序一致性：多个线程的整体执行可能是无序的，但对于单个线程而言执行是有序的，要保证任何一次读都能读到最近一次写入的数据，volatile可以阻止指令重排序，所以修饰的变量的程序属于顺序一致性；</p><p><img src="https://static001.geekbang.org/resource/image/6d/c6/6d70a02e0a4fb51259bd6ffcac0f75c6.jpg?wh=1638*488" alt=""></p><p>弱一致性：不能保证任何一次读都能读到最近一次写入的数据，但能保证最终可以读到写入的数据，单个写锁+无锁读，就是弱一致性的一种实现。</p><p>今天的加餐到这里就结束了，如有疑问，欢迎留言给我。也欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他一起学习。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/f8/4d/4b748ff1.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>THROW</span>
  </div>
  <div class="_2_QraFYR_0">老师您好，都说concurrenthashmap的get是弱一致性，但我不理解啊，volatile 修饰的变量读操作为什么会读不到最新的数据？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 我们知道Node&lt;k,v&gt;以及Node&lt;k,v&gt;的value是volatile修饰的，所以在一个线程对其进行修改后，另一个线程可以马上看到。<br>如果是一个新Node，那么就不能马上看到，虽然Node的数组table被volatile修饰，但是这样只是代表table的引用地址如果被修改，其他线程可以立马看到，并不代表table里的数据被修改立马可以看到。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-08 08:25:12</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/4d/49/28e73b9c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>明翼</span>
  </div>
  <div class="_2_QraFYR_0">早看到就好了😁，老师请教下这么多知识点你是怎么记住的？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 平时善于做笔记，除此之外，尝试将自己学到的知识点分享给其他人。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-10 06:22:41</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/b3/c5/7fc124e2.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Liam</span>
  </div>
  <div class="_2_QraFYR_0">老师好，请教一个问题：<br><br>文中举例，数据不一致是多核CPU的高速缓存不一致导致的，是否意味着单核CPU多线程操作就不会发生数据不一致呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 也会的，线程安全除了要保证可见性，还需要保证原子性、有序性。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-06 08:14:32</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/67/f4/9a1feb59.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>钱</span>
  </div>
  <div class="_2_QraFYR_0">数据只要在不同的地方，且存在写操作就可能存在不一致性。不管是各级缓存中还是分布式集群中的某些节点中，都有类似的问题。线程间的数据一致性问题，由操作系统来去保证，分布式系统中的数据一致性问题由分布式协议的实现者去保证，不过确实不好弄，令人头疼。<br>给老师点赞，讲的很棒，不过知识真是太多了，感觉越学越多，买的专栏都学不完，不学是不行的不进则退，如果英语好就占优势了，可以直接学习第一手的学习资料。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 坚持看英文文档，到最后你就能流利的读任何英文文档说明了</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-10 21:28:16</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/83/0f/b39a072d.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>青梅煮酒</span>
  </div>
  <div class="_2_QraFYR_0">老师，请问一下，每核CPU都有自己的L1和L2，那么L1和L2的主要区别是什么呢？为什么不能合到一起呢？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: L1\L2\L3三个缓存的作用和实现的技术是不一样的，L1的内存大小是非常有限的，所以很多时候在L1获取缓存数据的命中率非常低。为了提高CPU读取的速率，在L1没有命中的缓存，可以进入到L2进行获取，L2的容量要比L1大，但离CPU核心更远。但还是能提高CPU读取缓存数据的速率。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-16 19:00:22</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/7e/32/e569f729.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Lost In The Echo。</span>
  </div>
  <div class="_2_QraFYR_0">老师，请问强一致性和顺序一致性有什么区别吗？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 顺序一致性是指单个线程的执行的顺序性，强一致性则指的是多个线程在全局时钟下的执行的顺序性。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-06 20:43:48</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/79/4b/740f91ca.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>-W.LI-</span>
  </div>
  <div class="_2_QraFYR_0">老师容我问一个很基础的问题!父类private的属性会被子类继承么?子类创建的时候JVM给子类分配内存的时候，我看书上有说父类的属性会排在子类前面有可能穿插。可是没写是否会给子类分配父类的私有属性内存空间。子类创建的时候，会默认调用父类的无参构造器。这时候就会实例化一个父类对象么?(如果父类没有无参构造器会报错或者需要显示调用父类的有参构造器)。如果每次实力子类对象的时候都会先创建一个父类对象的话，滥用继承。就会浪费很多内存是么?对象头就需要8字节了。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 子类会继承父类的private属性，但子类无法直接访问到private属性；<br>子类创建时，不会创建一个父类对象的，只是调用了父类的构造函数初始化而已。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-06 12:22:53</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/15/43/8e/2c1ce77b.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>菜菜</span>
  </div>
  <div class="_2_QraFYR_0">针对老师对留言1的回复，我想问下老师，Node&lt;k,v&gt;中除了value被volatile修饰了，next也被volatile修饰了呀，这样如果是新增新的Node的话，其他线程也不可以看到吗？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 获取节点时是通过key值获取，并不一定通过next获取，所以不能代表对应key值中的value是最新的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-10-08 16:02:18</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/b2/08/92f42622.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>尔冬橙</span>
  </div>
  <div class="_2_QraFYR_0">老师可以不可以讲一下threadlocal</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: mark</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-21 10:37:40</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/aobibE2ABHn3njdaHBY23hcZcIs71aRahryuUDcLghQqTjmwghEIgKYelBERlNK881MP0oRpWGnrQdscD85dZ9g/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>云封</span>
  </div>
  <div class="_2_QraFYR_0">老师，请问下，如果不存在操作共享变量的情况或者把共享产量存在redis中，多线程结果就不会发生由于指令重排而导致结果不一致的情况。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 指令重排序不一定是由于共享变量导致的，这块需要结合具体的场景分析。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-07 12:10:31</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>InnerPeace</span>
  </div>
  <div class="_2_QraFYR_0">对于顺序一致性有点疑问。如果是单线程，无论是读还是写，都是串行，所以任何一次读都能读到最近一次写，这是显而易见的，为什么叫顺序一致性呢？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-01-07 22:22:53</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/c5/2d/1eebfc3c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>GaGi</span>
  </div>
  <div class="_2_QraFYR_0">老师，图中CPU多级缓存图中，L3缓存应该是多个CPU核心共用的</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-05-02 12:39:24</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/d1/5f/d0613359.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>鱼</span>
  </div>
  <div class="_2_QraFYR_0">老师，我指出一个错误，时序图中最后flag=true不是false</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 收到，谢谢提醒</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-10-25 09:35:20</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/79/4b/740f91ca.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>-W.LI-</span>
  </div>
  <div class="_2_QraFYR_0">老师好volatile+cas是强一致性么?。L1直接刷回主存，L2和L3需要做什么操作么?开头说每一级都是上一级的子集来着。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: cas+volatile可以解决单个变量的强一致性问题。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-07 16:12:59</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/14/ac/62/37912d51.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>东方奇骥</span>
  </div>
  <div class="_2_QraFYR_0">上面例子，flag加volatile修饰，根据happens before中的顺序性选择和volatile的原则，就能保证另一个线程读到写入的值了。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的，volatile除了可以保证变量的可见性，可以阻止局部指令重排序。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-06 21:28:40</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Geek_7d2c1c</span>
  </div>
  <div class="_2_QraFYR_0">int x = 1;&#47;&#47;步骤1：加载x变量的内存地址到寄存器中，加载1到寄存器中，CPU通过mov指令把1写入到寄存器指定的内存中<br>请问「CPU通过mov指令把1写入到寄存器指定的内存中」是对「加载1到寄存器中」的阐述吗？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2023-10-05 10:58:31</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/1a/d8/5d/07dfb3b5.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>杯莫停</span>
  </div>
  <div class="_2_QraFYR_0">“CPU 缓存可以分为一级缓存（L1）、二级缓存（L2）和三级缓存（L3），每一级缓存中所储存的全部数据都是下一级缓存的一部分” 就是说L1中的数据在L2中也有一份？为什么要这么设计，L2存增量不就好了吗？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2022-07-27 14:22:42</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/1f/4c/dd/c6035349.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Bumblebee</span>
  </div>
  <div class="_2_QraFYR_0">我觉得concurrenthashmap的get是弱一致性，可以这么理解，写操作是加锁的，get操作是无锁的，因此get操作有可能拿到写操作的中间值，因此是弱一致性（尽管volatile保证了可见性）；<br><br><br>希望老师能点评一下，不知道这样理解对不对；</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2022-06-01 21:57:16</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/d8/ee/6e7c2264.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Only now</span>
  </div>
  <div class="_2_QraFYR_0">关于存储模型这一节，通过实验结果很难接受。还请老师解答。<br>在实验中，非volatile共享堆变量，是一个bool值， 使用x86_64机器， Oracle jdk 1.8进行测试。<br><br>extern boolean stop = false;<br>new Thread(()-&gt;{<br>  while(!stop){}<br>  System.out.println(&quot;over&quot;);<br>}).start();<br>System.sleep(100L);<br>stop = true;<br><br>这样一段代码实际上不会停下来。按照三级缓存的这个说法，当内核在进行线程调度时会失效缓存，所以当线程被再次调入执行，它就应该可以看到stop更改的值，因为这个时候缓存已经更换过了。<br>然而事实并不会发现打印输出并停下来。<br><br>另有，缓存一致性协议EMSI应可以同步缓存，实际上也没效果。</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-11-03 17:04:26</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/db/26/3c8d68fb.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>天使梦泪</span>
  </div>
  <div class="_2_QraFYR_0">老师好，俩个线程共同执行X共享变量，有种结果是2,1，这个结果是怎么出现的哈？可以帮分析下么？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-07-07 18:24:25</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/%E5%8A%A0%E9%A4%90%20_%20%E4%BB%80%E4%B9%88%E6%98%AF%E6%95%B0%E6%8D%AE%E7%9A%84%E5%BC%BA%E3%80%81%E5%BC%B1%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%9F/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/20 _ 答疑课堂：模块三热点问题解答" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/20%20_%20%E7%AD%94%E7%96%91%E8%AF%BE%E5%A0%82%EF%BC%9A%E6%A8%A1%E5%9D%97%E4%B8%89%E7%83%AD%E7%82%B9%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94/">极客时间/Java性能调优实战/20 _ 答疑课堂：模块三热点问题解答</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/20%20_%20%E7%AD%94%E7%96%91%E8%AF%BE%E5%A0%82%EF%BC%9A%E6%A8%A1%E5%9D%97%E4%B8%89%E7%83%AD%E7%82%B9%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:07.852Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="20 _ 答疑课堂：模块三热点问题解答" src="https://static001.geekbang.org/resource/audio/1a/a9/1a5716c9c47742935200869724cee5a9.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>不知不觉“多线程性能优化“已经讲完了，今天这讲我来解答下各位同学在这个模块集中提出的两大问题，<span class="orange">第一个是有关监测上下文切换异常的命令排查工具，第二个是有关blockingQueue的内容。</span></p><p>也欢迎你积极留言给我，让我知晓你想了解的内容，或者说出你的困惑，我们共同探讨。下面我就直接切入今天的主题了。</p><h2>使用系统命令查看上下文切换</h2><p>在第15讲中我提到了上下文切换，其中有用到一些工具进行监测，由于篇幅关系就没有详细介绍，今天我就补充总结几个常用的工具给你。</p><h3>1. Linux命令行工具之vmstat命令</h3><p>vmstat是一款指定采样周期和次数的功能性监测工具，我们可以<span class="orange">使用它监控进程上下文切换的情况。</span></p><p><img src="https://static001.geekbang.org/resource/image/13/71/13eeee053c553863b3bdd95c07cb3b71.jpg?wh=1362*166" alt=""></p><p>vmstat 1 3  命令行代表每秒收集一次性能指标，总共获取3次。以下为上图中各个性能指标的注释：</p><ul>
<li><strong>procs</strong><br>
r：等待运行的进程数<br>
b：处于非中断睡眠状态的进程数</li>
<li><strong>memory</strong><br>
swpd：虚拟内存使用情况<br>
free：空闲的内存<br>
buff：用来作为缓冲的内存数<br>
cache：缓存大小</li>
<li><strong>swap</strong><br>
si：从磁盘交换到内存的交换页数量<br>
so：从内存交换到磁盘的交换页数量</li>
<li><strong>io</strong><br>
bi：每秒从块读取数据的量<br>
bo：每秒写入块数据的量</li>
<li><strong>system</strong><br>
in：每秒中断数<br>
cs：每秒上下文切换次数</li>
<li><strong>cpu</strong><br>
us：用户CPU使用时间<br>
sy：内核CPU系统使用时间<br>
id：空闲时间<br>
wa：等待I/O时间<br>
st：运行虚拟机窃取的时间</li>
</ul><!-- [[[read_end]]] --><h3>2. Linux命令行工具之pidstat命令</h3><p>我们通过上述的vmstat命令只能观察到哪个进程的上下文切换出现了异常，那如果是要查看哪个线程的上下文出现了异常呢？</p><p><span class="orange">pidstat命令就可以帮助我们监测到具体线程的上下文切换。</span>pidstat是Sysstat中一个组件，也是一款功能强大的性能监测工具。我们可以通过命令  yum install sysstat  安装该监控组件。</p><p>通过pidstat -help命令，我们可以查看到有以下几个常用参数可以监测线程的性能：</p><p><img src="https://static001.geekbang.org/resource/image/7a/d1/7a93cba1673119e4c9162a29e9875dd1.jpg?wh=1316*170" alt=""></p><p>常用参数：</p><ul>
<li>-u：默认参数，显示各个进程的cpu使用情况；</li>
<li>-r：显示各个进程的内存使用情况；</li>
<li>-d：显示各个进程的I/O使用情况；</li>
<li>-w：显示每个进程的上下文切换情况；</li>
<li>-p：指定进程号；</li>
<li>-t：显示进程中线程的统计信息</li>
</ul><p>首先，通过pidstat -w -p pid 命令行，我们可以查看到进程的上下文切换：</p><p><img src="https://static001.geekbang.org/resource/image/3e/4f/3e6cee25e85826aa5d4f8f480535234f.jpg?wh=1524*140" alt=""></p><ul>
<li>cswch/s：每秒主动任务上下文切换数量</li>
<li>nvcswch/s：每秒被动任务上下文切换数量</li>
</ul><p>之后，通过pidstat -w -p pid -t 命令行，我们可以查看到具体线程的上下文切换：</p><p><img src="https://static001.geekbang.org/resource/image/72/6f/728b1634e3e9971307264b5736cb1c6f.jpg?wh=1572*918" alt=""></p><h3>3. JDK工具之jstack命令</h3><p>查看具体线程的上下文切换异常，我们还<span class="orange">可以使用jstack命令查看线程堆栈的运行情况</span>。jstack是JDK自带的线程堆栈分析工具，使用该命令可以查看或导出 Java 应用程序中的线程堆栈信息。</p><p>jstack最常用的功能就是使用 jstack pid 命令查看线程堆栈信息，通常是结合pidstat -p pid -t一起查看具体线程的状态，也经常用来排查一些死锁的异常。</p><p><img src="https://static001.geekbang.org/resource/image/0e/1d/0e61a2f4eb945f5a26bd7987d0babd1d.jpg?wh=2224*382" alt=""></p><p>每个线程堆栈的信息中，都可以查看到线程ID、线程状态（wait、sleep、running等状态）以及是否持有锁等。</p><p>我们可以通过jstack 16079 &gt; /usr/dump将线程堆栈信息日志dump下来，之后打开dump文件，通过查看线程的状态变化，就可以找出导致上下文切换异常的具体原因。例如，系统出现了大量处于BLOCKED状态的线程，我们就需要立刻分析代码找出原因。</p><h2>多线程队列</h2><p>针对这讲的第一个问题，一份上下文切换的命令排查工具就总结完了。下面我来解答第二个问题，是在17讲中呼声比较高的有关blockingQueue的内容。</p><p>在Java多线程应用中，特别是在线程池中，队列的使用率非常高。Java提供的线程安全队列又分为了阻塞队列和非阻塞队列。</p><h3>1.阻塞队列</h3><p>我们先来看下阻塞队列。阻塞队列可以很好地支持生产者和消费者模式的相互等待，当队列为空的时候，消费线程会阻塞等待队列不为空；当队列满了的时候，生产线程会阻塞直到队列不满。</p><p>在Java线程池中，也用到了阻塞队列。当创建的线程数量超过核心线程数时，新建的任务将会被放到阻塞队列中。我们可以根据自己的业务需求来选择使用哪一种阻塞队列，阻塞队列通常包括以下几种：</p><ul>
<li><strong> ArrayBlockingQueue：</strong>一个基于数组结构实现的有界阻塞队列，按 FIFO（先进先出）原则对元素进行排序，使用ReentrantLock、Condition来实现线程安全；</li>
<li><strong> LinkedBlockingQueue：</strong>一个基于链表结构实现的阻塞队列，同样按FIFO （先进先出） 原则对元素进行排序，使用ReentrantLock、Condition来实现线程安全，吞吐量通常要高于ArrayBlockingQueue；</li>
<li><strong>PriorityBlockingQueue：</strong>一个具有优先级的无限阻塞队列，基于二叉堆结构实现的无界限（最大值Integer.MAX_VALUE - 8）阻塞队列，队列没有实现排序，但每当有数据变更时，都会将最小或最大的数据放在堆最上面的节点上，该队列也是使用了ReentrantLock、Condition实现的线程安全；</li>
<li><strong>DelayQueue：</strong>一个支持延时获取元素的无界阻塞队列，基于PriorityBlockingQueue扩展实现，与其不同的是实现了Delay延时接口；</li>
<li><strong>SynchronousQueue：</strong>一个不存储多个元素的阻塞队列，每次进行放入数据时, 必须等待相应的消费者取走数据后，才可以再次放入数据，该队列使用了两种模式来管理元素，一种是使用先进先出的队列，一种是使用后进先出的栈，使用哪种模式可以通过构造函数来指定。</li>
</ul><p>Java线程池Executors还实现了以下四种类型的ThreadPoolExecutor，分别对应以上队列，详情如下：</p><p><img src="https://static001.geekbang.org/resource/image/59/da/59e1d01c8a60fe722aae01db86a913da.jpg?wh=958*332" alt=""></p><h3>2.非阻塞队列</h3><p>我们常用的线程安全的非阻塞队列是ConcurrentLinkedQueue，它是一种无界线程安全队列(FIFO)，基于链表结构实现，利用CAS乐观锁来保证线程安全。</p><p>下面我们通过源码来分析下该队列的构造、入列以及出列的具体实现。</p><p><strong>构造函数：</strong>ConcurrentLinkedQueue由head 、tail节点组成，每个节点（Node）由节点元素（item）和指向下一个节点的引用 (next) 组成，节点与节点之间通过 next 关联，从而组成一张链表结构的队列。在队列初始化时， head  节点存储的元素为空，tail 节点等于 head 节点。</p><pre><code>public ConcurrentLinkedQueue() {
   head = tail = new Node&lt;E&gt;(null);
}

private static class Node&lt;E&gt; {
        volatile E item;
        volatile Node&lt;E&gt; next;
            .
            .
}
</code></pre><p><strong>入列：</strong>当一个线程入列一个数据时，会将该数据封装成一个Node节点，并先获取到队列的队尾节点，当确定此时队尾节点的next值为null之后，再通过CAS将新队尾节点的next值设为新节点。此时p != t，也就是设置next值成功，然后再通过CAS将队尾节点设置为当前节点即可。</p><pre><code>public boolean offer(E e) {
        checkNotNull(e);
        //创建入队节点
        final Node&lt;E&gt; newNode = new Node&lt;E&gt;(e);
        //t，p为尾节点，默认相等，采用失败即重试的方式，直到入队成功         
        for (Node&lt;E&gt; t = tail, p = t;;) {
            //获取队尾节点的下一个节点
            Node&lt;E&gt; q = p.next;
            //如果q为null，则代表p就是队尾节点
            if (q == null) {
                //将入列节点设置为当前队尾节点的next节点
                if (p.casNext(null, newNode)) {
                    //判断tail节点和p节点距离达到两个节点
                    if (p != t) // hop two nodes at a time
                        //如果tail不是尾节点则将入队节点设置为tail。
                        // 如果失败了，那么说明有其他线程已经把tail移动过 
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
            }
            // 如果p节点等于p的next节点，则说明p节点和q节点都为空，表示队列刚初始化，所以返回  
            else if (p == q)
                p = (t != (t = tail)) ? t : head;
            else
                // Check for tail updates after two hops.
                p = (p != t &amp;&amp; t != (t = tail)) ? t : q;
        }
    }
</code></pre><p><strong>出列：</strong>首先获取head节点，并判断item是否为null，如果为空，则表示已经有一个线程刚刚进行了出列操作，然后更新head节点；如果不为空，则使用CAS操作将head节点设置为null，CAS就会成功地直接返回节点元素，否则还是更新head节点。</p><pre><code>    public E poll() {
        // 设置起始点
        restartFromHead:
        for (;;) {
            //p获取head节点
            for (Node&lt;E&gt; h = head, p = h, q;;) {
                //获取头节点元素
                E item = p.item;
                //如果头节点元素不为null，通过cas设置p节点引用的元素为null
                if (item != null &amp;&amp; p.casItem(item, null)) {
                    // Successful CAS is the linearization point
                    // for item to be removed from this queue.
                    if (p != h) // hop two nodes at a time
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                //如果p节点的下一个节点为null，则说明这个队列为空，更新head结点
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                //节点出队失败，重新跳到restartFromHead来进行出队
                else if (p == q)
                    continue restartFromHead;
                else
                    p = q;
            }
        }
    }
</code></pre><p>ConcurrentLinkedQueue是基于CAS乐观锁实现的，在并发时的性能要好于其它阻塞队列，因此<span class="orange">很适合作为高并发场景下的排队队列。</span></p><p>今天的答疑就到这里，如果你还有其它问题，请在留言区中提出，我会一一解答。最后欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他加入讨论。</p><p><img src="https://static001.geekbang.org/resource/image/bb/67/bbe343640d6b708832c4133ec53ed967.jpg" alt="unpreview"></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/d0/69/5dbdc245.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>张德</span>
  </div>
  <div class="_2_QraFYR_0">老师  Disruptor是不是比ConcurrentLinkedQueue性能更强呢???</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的，Disruptor是一款性能更高的有界队列，利用了生产者消费者模式实现，并采用无锁算法、避免伪共享、RingBuffer等优化手段提升该队列框架性能。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-15 11:10:27</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/79/4b/740f91ca.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>-W.LI-</span>
  </div>
  <div class="_2_QraFYR_0">老师好!FGC正常情况多久一次比较合适啊?我们项目1.2天一次FGC老年代给了3G年轻代1G想吧年轻代给多点。有个定时任务，2小时一次用的线程池。给了40个线程并发请求4K次。设置了空闲回收策略回收核心线程。现在就是定时任务，每次都新建40个线程一张吃老年代内存。不设置回收这些线程不实用的那个吧小时就一直阻塞。怎么处理比较合适</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: GC在核心业务应用服务中越久发生越合适，且GC的时间不要太长。一般生产环境的FGC几天一次是比较正常的。40个线程是不是设置太大了，建议调小一些，当然需要你们具体压测验证下调小后的性能情况。<br><br>年轻代可以调大一些，如果年轻代太小，当MinorGC时，发现年轻代依然存活满对象，新的对象可能将无法放入到年轻代，则会通过分配担保机制提前转移年轻代的存活对象到老年代中，这样反而会增加老年代的负担。默认情况下老年代和新生代是2:1。建议没有特殊情况，不要固定设置老年代和新生代。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-04 16:12:45</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/b3/c5/7fc124e2.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Liam</span>
  </div>
  <div class="_2_QraFYR_0">我有2个问题想请教老师：<br><br>1 系统出现问题时我们一般会首先关注资源的使用情况，什么情况下可能是是上下文切换过多导致的呢？CPU消耗过高？<br><br>2 ConcurrentLinkedQueue是非阻塞的，是否意味着它会消耗过多的CPu</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: CPU消耗过高会引起上下文切换的增加，但并不代表这个就不正常了。正常情况下上下文切换在几百到几千，高峰时段会上升至几万，甚至几十万。<br><br>如果上下文长时间处于高位，这个时候我们就要注意了，这种情况有可能是某个线程长期占用CPU，例如之前我提到过的正则表达式出现的严重的回溯问题，就会在某一次回溯时，一直占用CPU，CPU的使用率高居不下，会导致上下文切换激增。<br><br>另外一种情况，就是之前你们的业务在高峰值出现的上下文切换在某个值，但是在业务迭代之后，高峰期的上下文切换的值异常高于之前的监控值。比如，我之前说的线程大小调整，导致了高峰期的上下文高出了十几倍之多。<br><br>ConcurrentLinkedQueue CAS操作会消耗CPU，但会及时释放，这不足以影响到系统的整体性能。<br><br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-04 08:11:49</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLCrJQ4AZe8VrDkR6IO03V4Tda9WexVT4zZiahBjLSYOnZb1Y49JvD2f70uQwYSMibUMQvib9NmGxEiag/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Dowen Liu</span>
  </div>
  <div class="_2_QraFYR_0">LinkedBlockingQueue为什么会比ArrayBlockingQueue 快？我记得ArrayBlockingQueue内部应该是循环数组的方式，没有数据搬移和复制的。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: LinkedBlockingQueue实现了读写锁分离，所以在一些场景下要比ArrayBlockingQueue快。ArrayBlockingQueue就是正常的锁实现，没有使用复制</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-03-18 09:00:39</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/7e/bb/019c18fc.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>徐云天</span>
  </div>
  <div class="_2_QraFYR_0">云里雾里的：有篇文章详解了ConcurrentLinkedQueue<br><br>https:&#47;&#47;www.jianshu.com&#47;p&#47;08e8b0c424c0</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-11-10 10:23:21</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/63/8b/e94448be.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>咬你</span>
  </div>
  <div class="_2_QraFYR_0">老师，通过vmstat参数获取的参数，可否结合一些真实场景，分析下什么样的数据范围属于正常范围，出现什么样的参数，我们就需要重点关注</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 一般系统出现性能瓶颈，可以结果上下文切换指标进行分析。在之前15讲中，我已经通过一个真实案例讲解了，可以参考下，有什么问题欢迎沟通。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-04 09:41:45</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/67/f4/9a1feb59.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>钱</span>
  </div>
  <div class="_2_QraFYR_0">打卡+点赞</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-10 20:57:26</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/c5/6a/7f858f1f.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>白不吃</span>
  </div>
  <div class="_2_QraFYR_0">CAS是不是比加锁的方案，更消耗CPU资源一些</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2022-03-14 19:06:26</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/f3/69/7039d03f.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>渔村蓝</span>
  </div>
  <div class="_2_QraFYR_0">一个不存储多个元素的阻塞队列，每次进行放入数据时, 必须等待相应的消费者取走数据后，才可以再次放入数据。都不存储多个了，还哪来的先进先出？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-07-06 12:30:27</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/07/d2/0d7ee298.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>惘 闻</span>
  </div>
  <div class="_2_QraFYR_0">Node t = tail, p = t;<br>p和t都是指向的tail指向的堆空间.<br>p.casNext  修改的是tail节点的下一个节点.<br>为什么修改下一个节点之后就会出现p!=t的情况呢?这俩不还是tail指向的堆内存空间吗?<br>我好晕啊老师...</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-12-29 11:08:16</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/07/d2/0d7ee298.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>惘 闻</span>
  </div>
  <div class="_2_QraFYR_0">老师代码里的 if (p != t) 不太明白. Node t = tail, p = t; 这样的话 p不是永远都等于t吗?   </div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-12-29 11:03:47</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/07/d2/0d7ee298.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>惘 闻</span>
  </div>
  <div class="_2_QraFYR_0">SynchronousQueue：一个不存储多个元素的阻塞队列，每次进行放入数据时, 必须等待相应的消费者取走数据后，才可以再次放入数据，该队列使用了两种模式来管理元素，一种是使用先进先出的队列，一种是使用后进先出的栈，使用哪种模式可以通过构造函数来指定。<br>老师这个队列就存放一个元素,先进先出还是先进后出有意义吗?</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-12-29 10:53:15</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/1e/3a/5b21c01c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>nightmare</span>
  </div>
  <div class="_2_QraFYR_0">性能好是一方面，如果是抢购应用在需要用有界队列</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-04 08:14:58</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/20%20_%20%E7%AD%94%E7%96%91%E8%AF%BE%E5%A0%82%EF%BC%9A%E6%A8%A1%E5%9D%97%E4%B8%89%E7%83%AD%E7%82%B9%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/19 _ 如何用协程来优化多线程业务？" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/19%20_%20%E5%A6%82%E4%BD%95%E7%94%A8%E5%8D%8F%E7%A8%8B%E6%9D%A5%E4%BC%98%E5%8C%96%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%9A%E5%8A%A1%EF%BC%9F/">极客时间/Java性能调优实战/19 _ 如何用协程来优化多线程业务？</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/19%20_%20%E5%A6%82%E4%BD%95%E7%94%A8%E5%8D%8F%E7%A8%8B%E6%9D%A5%E4%BC%98%E5%8C%96%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%9A%E5%8A%A1%EF%BC%9F/" class="archive-article-date">
  	<time datetime="2024-02-27T16:32:02.044Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="19 _ 如何用协程来优化多线程业务？" src="https://static001.geekbang.org/resource/audio/8c/70/8cf1eb4d5700805bb5faed3a97546070.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>近一两年，国内很多互联网公司开始使用或转型Go语言，其中一个很重要的原因就是Go语言优越的性能表现，而这个优势与Go实现的轻量级线程Goroutines（协程Coroutine）不无关系。那么Go协程的实现与Java线程的实现有什么区别呢？</p><h2>线程实现模型</h2><p>了解协程和线程的区别之前，我们不妨先来了解下底层实现线程几种方式，为后面的学习打个基础。</p><p>实现线程主要有三种方式：轻量级进程和内核线程一对一相互映射实现的1:1线程模型、用户线程和内核线程实现的N:1线程模型以及用户线程和轻量级进程混合实现的N:M线程模型。</p><h3>1:1线程模型</h3><p>以上我提到的内核线程（Kernel-Level Thread, KLT）是由操作系统内核支持的线程，内核通过调度器对线程进行调度，并负责完成线程的切换。</p><p>我们知道在Linux操作系统编程中，往往都是通过fork()函数创建一个子进程来代表一个内核中的线程。一个进程调用fork()函数后，系统会先给新的进程分配资源，例如，存储数据和代码的空间。然后把原来进程的所有值都复制到新的进程中，只有少数值与原来进程的值（比如PID）不同，这相当于复制了一个主进程。</p><p>采用fork()创建子进程的方式来实现并行运行，会产生大量冗余数据，即占用大量内存空间，又消耗大量CPU时间用来初始化内存空间以及复制数据。</p><!-- [[[read_end]]] --><p>如果是一份一样的数据，为什么不共享主进程的这一份数据呢？这时候轻量级进程（Light Weight Process，即LWP）出现了。</p><p>相对于fork()系统调用创建的线程来说，LWP使用clone()系统调用创建线程，该函数是将部分父进程的资源的数据结构进行复制，复制内容可选，且没有被复制的资源可以通过指针共享给子进程。因此，轻量级进程的运行单元更小，运行速度更快。LWP是跟内核线程一对一映射的，每个LWP都是由一个内核线程支持。</p><h3>N:1线程模型</h3><p>1:1线程模型由于跟内核是一对一映射，所以在线程创建、切换上都存在用户态和内核态的切换，性能开销比较大。除此之外，它还存在局限性，主要就是指系统的资源有限，不能支持创建大量的LWP。</p><p>N:1线程模型就可以很好地解决1:1线程模型的这两个问题。</p><p>该线程模型是在用户空间完成了线程的创建、同步、销毁和调度，已经不需要内核的帮助了，也就是说在线程创建、同步、销毁的过程中不会产生用户态和内核态的空间切换，因此线程的操作非常快速且低消耗。</p><h3>N:M线程模型</h3><p>N:1线程模型的缺点在于操作系统不能感知用户态的线程，因此容易造成某一个线程进行系统调用内核线程时被阻塞，从而导致整个进程被阻塞。</p><p>N:M线程模型是基于上述两种线程模型实现的一种混合线程管理模型，即支持用户态线程通过LWP与内核线程连接，用户态的线程数量和内核态的LWP数量是N:M的映射关系。</p><p><strong>了解完这三个线程模型，你就可以清楚地了解到Go协程的实现与Java线程的实现有什么区别了。</strong></p><p>JDK 1.8 Thread.java  中  Thread#start  方法的实现，实际上是通过Native调用start0方法实现的；在Linux下， JVM Thread的实现是基于pthread_create实现的，而pthread_create实际上是调用了clone()完成系统调用创建线程的。</p><p>所以，目前Java在Linux操作系统下采用的是用户线程加轻量级线程，一个用户线程映射到一个内核线程，即1:1线程模型。由于线程是通过内核调度，从一个线程切换到另一个线程就涉及到了上下文切换。</p><p>而Go语言是使用了N:M线程模型实现了自己的调度器，它在N个内核线程上多路复用（或调度）M个协程，协程的上下文切换是在用户态由协程调度器完成的，因此不需要陷入内核，相比之下，这个代价就很小了。</p><h2>协程的实现原理</h2><p>协程不只在Go语言中实现了，其实目前大部分语言都实现了自己的一套协程，包括C#、erlang、python、lua、javascript、ruby等。</p><p>相对于协程，你可能对进程和线程更为熟悉。进程一般代表一个应用服务，在一个应用服务中可以创建多个线程，而协程与进程、线程的概念不一样，<span class="orange">我们可以将协程看作是一个类函数或者一块函数中的代码，</span>我们可以在一个主线程里面轻松创建多个协程。</p><p>程序调用协程与调用函数不一样的是，协程可以通过暂停或者阻塞的方式将协程的执行挂起，而其它协程可以继续执行。这里的挂起只是在程序中（用户态）的挂起，同时将代码执行权转让给其它协程使用，待获取执行权的协程执行完成之后，将从挂起点唤醒挂起的协程。 协程的挂起和唤醒是通过一个调度器来完成的。</p><p>结合下图，你可以更清楚地了解到基于N:M线程模型实现的协程是如何工作的。</p><p>假设程序中默认创建两个线程为协程使用，在主线程中创建协程ABCD…，分别存储在就绪队列中，调度器首先会分配一个工作线程A执行协程A，另外一个工作线程B执行协程B，其它创建的协程将会放在队列中进行排队等待。</p><p><img src="https://static001.geekbang.org/resource/image/9b/72/9b0a301337fa868eab1b9d32e6fcbd72.jpg?wh=666*870" alt=""></p><p>当协程A调用暂停方法或被阻塞时，协程A会进入到挂起队列，调度器会调用等待队列中的其它协程抢占线程A执行。当协程A被唤醒时，它需要重新进入到就绪队列中，通过调度器抢占线程，如果抢占成功，就继续执行协程A，失败则继续等待抢占线程。</p><p><img src="https://static001.geekbang.org/resource/image/be/94/bebdfb057a9243e640515900eec4ed94.jpg?wh=656*874" alt=""></p><p>相比线程，协程少了由于同步资源竞争带来的CPU上下文切换，I/O密集型的应用比较适合使用，特别是在网络请求中，有较多的时间在等待后端响应，协程可以保证线程不会阻塞在等待网络响应中，充分利用了多核多线程的能力。而对于CPU密集型的应用，由于在多数情况下CPU都比较繁忙，协程的优势就不是特别明显了。</p><h2>Kilim协程框架</h2><p>虽然这么多的语言都实现了协程，但目前Java原生语言暂时还不支持协程。不过你也不用泄气，我们可以通过协程框架在Java中使用协程。</p><p>目前Kilim协程框架在Java中应用得比较多，通过这个框架，开发人员就可以低成本地在Java中使用协程了。</p><p>在Java中引入 <a href="https://github.com/kilim/kilim" target="_blank" rel="noopener">Kilim</a> ，和我们平时引入第三方组件不太一样，除了引入jar包之外，还需要通过Kilim提供的织入（Weaver）工具对Java代码编译生成的字节码进行增强处理，比如，识别哪些方式是可暂停的，对相关的方法添加上下文处理。通常有以下四种方式可以实现这种织入操作：</p><ul>
<li>在编译时使用maven插件；</li>
<li>在运行时调用kilim.tools.Weaver工具；</li>
<li>在运行时使用kilim.tools.Kilim invoking调用Kilim的类文件；</li>
<li>在main函数添加 if (kilim.tools.Kilim.trampoline(false,args)) return。</li>
</ul><p>Kilim框架包含了四个核心组件，分别为：任务载体（Task）、任务上下文（Fiber）、任务调度器（Scheduler）以及通信载体（Mailbox）。</p><p><img src="https://static001.geekbang.org/resource/image/20/13/20e81165d99c5fc1a55424156e15ff13.jpg?wh=880*690" alt=""></p><p>Task对象主要用来执行业务逻辑，我们可以把这个比作多线程的Thread，与Thread类似，Task中也有一个run方法，不过在Task中方法名为execute，我们可以将协程里面要做的业务逻辑操作写在execute方法中。</p><p>与Thread实现的线程一样，Task实现的协程也有状态，包括：Ready、Running、Pausing、Paused以及Done总共五种。Task对象被创建后，处于Ready状态，在调用execute()方法后，协程处于Running状态，在运行期间，协程可以被暂停，暂停中的状态为Pausing，暂停后的状态为Paused，暂停后的协程可以被再次唤醒。协程正常结束后的状态为Done。</p><p>Fiber对象与Java的线程栈类似，主要用来维护Task的执行堆栈，Fiber是实现N:M线程映射的关键。</p><p>Scheduler是Kilim实现协程的核心调度器，Scheduler负责分派Task给指定的工作者线程WorkerThread执行，工作者线程WorkerThread默认初始化个数为机器的CPU个数。</p><p>Mailbox对象类似一个邮箱，协程之间可以依靠邮箱来进行通信和数据共享。协程与线程最大的不同就是，线程是通过共享内存来实现数据共享，而协程是使用了通信的方式来实现了数据共享，主要就是为了避免内存共享数据而带来的线程安全问题。</p><h2>协程与线程的性能比较</h2><p>接下来，我们通过一个简单的生产者和消费者的案例，来对比下协程和线程的性能。可通过 <a href="https://github.com/nickliuchao/coroutine" target="_blank" rel="noopener">Github</a> 下载本地运行代码。</p><p>Java多线程实现源码：</p><pre><code>public class MyThread {
	private static Integer count = 0;//
	private static final Integer FULL = 10; //最大生产数量
	private static String LOCK = &quot;lock&quot;; //资源锁

	public static void main(String[] args) {
		MyThread test1 = new MyThread();

		long start = System.currentTimeMillis();

		List&lt;Thread&gt; list = new ArrayList&lt;Thread&gt;();
		for (int i = 0; i &lt; 1000; i++) {//创建五个生产者线程
			Thread thread = new Thread(test1.new Producer());
			thread.start();
			list.add(thread);
		}

		for (int i = 0; i &lt; 1000; i++) {//创建五个消费者线程
			Thread thread = new Thread(test1.new Consumer());
			thread.start();
			list.add(thread);
		}

		try {
			for (Thread thread : list) {
				thread.join();//等待所有线程执行完
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		long end = System.currentTimeMillis();
		System.out.println(&quot;子线程执行时长：&quot; + (end - start));
	}
    //生产者
	class Producer implements Runnable {
		public void run() {
			for (int i = 0; i &lt; 10; i++) {
				synchronized (LOCK) {
					while (count == FULL) {//当数量满了时
						try {
							LOCK.wait();
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
					count++;
					System.out.println(Thread.currentThread().getName() + &quot;生产者生产，目前总共有&quot; + count);
					LOCK.notifyAll();
				}
			}
		}
	}
    //消费者
	class Consumer implements Runnable {
		public void run() {
			for (int i = 0; i &lt; 10; i++) {
				synchronized (LOCK) {
					while (count == 0) {//当数量为零时   
						try {
							LOCK.wait();
						} catch (Exception e) {
						}
					}
					count--;
					System.out.println(Thread.currentThread().getName() + &quot;消费者消费，目前总共有&quot; + count);
					LOCK.notifyAll();
				}
			}
		}
	}
}
</code></pre><p>Kilim协程框架实现源码：</p><pre><code>public class Coroutine  {

		static Map&lt;Integer, Mailbox&lt;Integer&gt;&gt; mailMap = new HashMap&lt;Integer, Mailbox&lt;Integer&gt;&gt;();//为每个协程创建一个信箱，由于协程中不能多个消费者共用一个信箱，需要为每个消费者提供一个信箱，这也是协程通过通信来保证共享变量的线程安全的一种方式

	public static void main(String[] args) {

		if (kilim.tools.Kilim.trampoline(false,args)) return;
		Properties propes = new Properties();
		propes.setProperty(&quot;kilim.Scheduler.numThreads&quot;, &quot;1&quot;);//设置一个线程
		System.setProperties(propes);
		long startTime = System.currentTimeMillis();
		for (int i = 0; i &lt; 1000; i++) {//创建一千生产者
			Mailbox&lt;Integer&gt; mb = new Mailbox&lt;Integer&gt;(1, 10);
			new Producer(i, mb).start();
			mailMap.put(i, mb);
		}
		
		for (int i = 0; i &lt; 1000; i++) {//创建一千个消费者
			new Consumer(mailMap.get(i)).start();
		}
		
		Task.idledown();//开始运行
		
		 long endTime = System.currentTimeMillis();
	        
	     System.out.println( Thread.currentThread().getName()  + &quot;总计花费时长：&quot; + (endTime- startTime));
	}
	
}
</code></pre><pre><code>//生产者
public class Producer extends Task&lt;Object&gt; {

	Integer count = null;
	Mailbox&lt;Integer&gt; mb = null;

	public Producer(Integer count, Mailbox&lt;Integer&gt; mb) {
		this.count = count;
		this.mb = mb;
	}

	public void execute() throws Pausable {
		count = count*10;
		for (int i = 0; i &lt; 10; i++) {
			mb.put(count);//当空间不足时，阻塞协程线程
			System.out.println(Thread.currentThread().getName() + &quot;生产者生产，目前总共有&quot; + mb.size() + &quot;生产了：&quot; + count);
			count++;
		}
	}
}
</code></pre><pre><code>//消费者
public class Consumer extends Task&lt;Object&gt; {

	Mailbox&lt;Integer&gt; mb = null;

	public Consumer(Mailbox&lt;Integer&gt; mb) {
		this.mb = mb;
	}

	/**
	 * 执行
	 */
	public void execute() throws Pausable {
		Integer c = null;
		for (int i = 0; i &lt; 10000; i++)  {
			c = mb.get();//获取消息，阻塞协程线程
			
			if (c == null) {
				System.out.println(&quot;计数&quot;);
			}else {
				System.out.println(Thread.currentThread().getName() + &quot;消费者消费，目前总共有&quot; + mb.size() + &quot;消费了：&quot; + c);
				c = null;
			}
		}
	}
}
</code></pre><p>在这个案例中，我创建了1000个生产者和1000个消费者，每个生产者生产10个产品，1000个消费者同时消费产品。我们可以看到两个例子运行的结果如下：</p><pre><code>多线程执行时长：2761
</code></pre><pre><code>协程执行时长：1050
</code></pre><p>通过上述性能对比，我们可以发现：在有严重阻塞的场景下，协程的性能更胜一筹。其实，<span class="orange">I/O阻塞型场景也就是协程在Java中的主要应用。</span></p><h2>总结</h2><p>协程和线程密切相关，协程可以认为是运行在线程上的代码块，协程提供的挂起操作会使协程暂停执行，而不会导致线程阻塞。</p><p>协程又是一种轻量级资源，即使创建了上千个协程，对于系统来说也不是很大的负担，但如果在程序中创建上千个线程，那系统可真就压力山大了。可以说，<span class="orange">协程的设计方式极大地提高了线程的使用率。</span></p><p>通过今天的学习，当其他人侃侃而谈Go语言在网络编程中的优势时，相信你不会一头雾水。学习Java的我们也不要觉得，协程离我们很遥远了。协程是一种设计思想，不仅仅局限于某一门语言，况且Java已经可以借助协程框架实现协程了。</p><p>但话说回来，协程还是在Go语言中的应用较为成熟，在Java中的协程目前还不是很稳定，重点是缺乏大型项目的验证，可以说Java的协程设计还有很长的路要走。</p><h2>思考题</h2><p>在Java中，除了Kilim框架，你知道还有其它协程框架也可以帮助Java实现协程吗？你使用过吗？</p><p>期待在留言区看到你的见解。也欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他一起讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/b2/b3/798a4bb2.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>帽子丨影</span>
  </div>
  <div class="_2_QraFYR_0">文中提到协程有个队列，最终还是需要主线程来调度执行。那是不是有点类似线程池。主线程是Worker线程，每一个协程就是一个Task。只不过主线程执行到的某个Task阻塞时会去执行下一个Task。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的，这样理解很清晰</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-16 15:08:24</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/18/cb/edb5a0a0.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>小橙橙</span>
  </div>
  <div class="_2_QraFYR_0">老师，以后的JAVA版本是不是也会自带协程功能？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的，Java未来的三个主要项目之一Loom项目 引入了被称为 fibers 的新型轻量级用户线程，甲骨文公司 Loom 项目技术负责人 Ron Pressler 在 QCon 伦敦 2019 大会上指出：“利用 fibers，如果我们确保其轻量化程度高于内核提供的线程，那么问题就得到了解决。大家将能够尽可能多地使用这些用户模式下的轻量级线程，且基本不会出现任何阻塞问题”。<br><br>具体的可以阅读以下openjdk官网链接：<br>https:&#47;&#47;cr.openjdk.java.net&#47;~rpressler&#47;loom&#47;Loom-Proposal.html</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-05 17:28:59</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/20/45/3c5548bb.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>~Wade</span>
  </div>
  <div class="_2_QraFYR_0">有一个java库叫Quasar Fiber ，通过javaagent技术可以实现轻量级线程 <br>官网：http:&#47;&#47;www.paralleluniverse.co&#47;quasar&#47;</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-24 09:32:49</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/7a/4b/1a7b36ab.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>欧星星</span>
  </div>
  <div class="_2_QraFYR_0">老师有几个疑问<br>1. 协程必须手动调用等待或阻塞才能被安排到等待队列去吗？还是说协程也可以跟线程一样会被随机丢到等待队列去每个协程也有个运行时间片？如果可以随机一般是如何实现的？<br>2. 协程之间的争抢基于什么实现的？我想的话可以使用CAS来实现没有抢到的再次被丢到等待队列不知道对不对。<br>3. 我看例子上邮箱里面没有数据时消费者协程没有类似线程的等待机制，这个要如何写呢？</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-02 13:01:06</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/67/f4/9a1feb59.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>钱</span>
  </div>
  <div class="_2_QraFYR_0">课前思考及问题<br>1：协程是啥？<br>2：协程更轻量怎么体现，它轻量在哪里？<br>3：为什么多协程没有高并发的问题？<br>4：协程真这么优秀，为啥没有全面支持或使用？换言之实现协程需要什么代价？<br>昨晚休息的太晚了，今天脑袋晕晕的，状态不佳感觉学完还是回答不好，课前的思考题。先跳过，回头再看一下。<br><br>课后思考及问题<br>1：协程与线程最大的不同就是，线程是通过共享内存来实现数据共享，而协程是使用了通信的方式来实现了数据共享，主要就是为了避免内存共享数据而带来的线程安全问题。<br>这里的通信方式具体指什么？它比共享内存通信的方式还要快嘛？每个协程间都需要建立这种通信方式不耗资源不影响性能嘛？<br><br>2：而 Go 语言是使用了 N:M 线程模型实现了自己的调度器，它在 N 个内核线程上多路复用（或调度）M 个协程，协程的上下文切换是在用户态由协程调度器完成的，因此不需要陷入内核，相比之下，这个代价就很小了。<br>协程的执行不需要CPU？若需要，就会存在切入切出，协程的上下文切换怎么能在用户态全部完成，这个没有理解？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 1、这里说的通信方式是指协程间的通信方式，是通过数据共享来实现的，不存在线程安全问题；<br>2、需要CPU，协程可以更充分的利用好CPU，不会来回切换资源。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-09-10 20:44:43</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/7b/57/a9b04544.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>QQ怪</span>
  </div>
  <div class="_2_QraFYR_0">老师讲协程讲的最深最易懂的一个，赞赞赞</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-02 09:19:50</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/24/5d/65e61dcb.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>学无涯</span>
  </div>
  <div class="_2_QraFYR_0">老师，读了今天的内容，我理解的意思是：<br>1.因为每个轻量级线程都有一个内核线程支持，而java中，每个用户线程对应一个轻量级线程，可以看作用户线程和支持轻量级线程的内核线程是一对一的，所以就说java线程模型是用户线程和内核线程一对一。<br>2.那这里轻量级线程属于内核线程吗，我看文中说的是由内核线程clone而来的，那它算内核线程吗？<br>请老师解答一下！</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 1、对的<br>2、属于用户线程，与内核线程一对一映射</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-02 08:51:54</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/15/8f/b1/7b697ed4.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>晓晨同学</span>
  </div>
  <div class="_2_QraFYR_0">请问老师，上面说N:1这种线程模型是当某个用户线程使用内核线程阻塞的时候会使整个进程下的所有用户线程阻塞，那我想为什么不能像N:M线程模型一样当某个用户线程阻塞时就挂起来从而去调度其他的用户线程呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 可以，只是实现成本的问题</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-11-13 21:18:48</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/fy6KTCIk6JPGdOYbvsXJ4h1zq8a6P6sHiau0t2hBxxZfSeE2ymJmWsnb7YzxUO3byr1n30UoYD2XrXDwlrQa6lQ/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Ericens</span>
  </div>
  <div class="_2_QraFYR_0">老师，请教个关于协程与线程的疑问。比如，a协程调用socket. read(),此时数据没有准备好，则继续调度b协程。<br><br>把协程换成线程，上面这个过程哪个更加轻量？协程还是线程？<br>我理解这个过程涉及的过程如下，都一样。<br>1.都有系统调用read()，从用户态切换到了内核态，<br>2.都有上下文切换。(不同协程的寄存器，和不同线程的寄存器)<br>3. 都要执行任务调度。协程调度或者线程调度。<br><br>那协程到底轻量在哪？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 协程更轻量级，协程是线程的子集，所以不会存在线程间的上下文切换，而仅仅是通过调度器来完成调度的。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-10-11 23:50:45</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/3d/ae/5eede757.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>葛阳</span>
  </div>
  <div class="_2_QraFYR_0">&quot;相对于 fork() 系统调用创建的线程来说，LWP 使用 clone() 系统调用创建线程，该函数是将部分父进程的资源的数据结构进行复制，复制内容可选，且没有被复制的资源可以通过指针共享给子进程。因此，轻量级进程的运行单元更小，运行速度更快。LWP 是跟内核线程一对一映射的，每个 LWP 都是由一个内核线程支持。&quot;  -- 这句话是不是有毛病，一会儿&quot;进程&quot;一会儿&quot;线程&quot;</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 这里的进程应该理解为线程，因为在unix编程中，通常以进程来代表线程的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-04-27 16:27:46</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/5a/08/2bef230f.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>慎独</span>
  </div>
  <div class="_2_QraFYR_0">老师，协程在高并发系统中使用过吗？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 协程可以胜任高并发场景，不过目前没有在线上的Java项目使用过协程</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-12-04 02:11:03</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/ec/13/49e98289.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>neohope</span>
  </div>
  <div class="_2_QraFYR_0">1. Java框架的话，Kilim有一段时间没有更新了，最近Quasar好像流行一些。<br>2. JVM里，可以混用Kotlin；或者是混用Scala+akka。<br>3. 未来版本的JDK也会逐步支持协程，但现在的实验版本好像只是在Linux下支持的比较好。<br>4 .不过说实话，现在的netty框架，在一定意义上工作原理和协程也有一些相似之处。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: netty在处理同步业务场景下，特别是业务处理比较耗时的场景下，并发处理能力有限。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-11-22 14:46:42</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/79/4b/740f91ca.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>-W.LI-</span>
  </div>
  <div class="_2_QraFYR_0">老师好!1:1,N:1，N:M的线程模型。总感觉上学的时候有讲可是又想不起来。谢谢老师的讲解。不过还是有些不明白的地方。<br>Java是采用1:1的映射方式。一个Java线程就对应一个内核线程。用户态和内核态的切换和这个映射模型有关系么(用户态和内核态，和用户线程内核线程是否有关系)?<br>从用户态切换为内核态的时候具体做了哪些操作?之前讲IO模型时老师讲了，用户空间和内核空间，多次数据拷贝。和用户线程内核线程有什么联系么?后面会讲么?</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-02 13:23:37</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/1e/3a/5b21c01c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>nightmare</span>
  </div>
  <div class="_2_QraFYR_0">终于弄懂协程和线程的区别了，协程基于N：M的线程模型实现，M个协程被调度器调度，实际上也是被内核线程执行，不过由于需要的内核线程少，一个内核线程可以执行多个协程，占用资源少，而且上下文切换也更加少，而基于线程的1：1模型只有有阻塞就会有上下文切换</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-02 09:09:48</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Geek_b71aed</span>
  </div>
  <div class="_2_QraFYR_0">I&#47;O 阻塞型场景也就是协程在 Java 中的主要应用，这句话不对吧，协程遇到阻塞性IO的时候，宿主线程都会阻塞，因为操作系统对于协程是无感知的，只能一刀切线程。</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2023-05-09 16:29:54</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/2e/27/b4/df65c0f7.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>| ~浑蛋~</span>
  </div>
  <div class="_2_QraFYR_0">协程的上下文怎么维护呢</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2022-12-11 21:41:49</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/1a/d8/5d/07dfb3b5.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>杯莫停</span>
  </div>
  <div class="_2_QraFYR_0">协程其实还是要利用线程来实现多路复用的，因为操作系统只认线程。其实跟netty的多线程模型有些类似，只是协程干了阻塞IO的事，少了上下文切换。</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2022-07-28 18:20:10</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/5a/c1/6df65e0a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>190coder</span>
  </div>
  <div class="_2_QraFYR_0">java 里的协程就是玩具，在线程资源足够多的时候，性能和效率根本比不过线程</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-09-11 10:04:52</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/f3/69/7039d03f.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>渔村蓝</span>
  </div>
  <div class="_2_QraFYR_0">每个协程都有自己的mailbox，为啥还能共享变量？如果不能共享变量，这两个测试程序根本体现不出啥。</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-07-05 18:10:59</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/x86UN2kFbJGGwiaw7yeVtyaf05y5eZmdOciaAF09WEBRVicbPGsej1b62UAH3icjeJqvibVc6aqB0EuFwDicicKKcF47w/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Eco</span>
  </div>
  <div class="_2_QraFYR_0">好像有个叫akka的框架不知道是不是协程的思想。还有老师在上面讲协程调度哪里，感觉有点像线程池呢。就指定数量的线程去执行远远大于线程数的Task，只不过这里的Task有状态还能阻塞。</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2021-03-08 17:39:02</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/19%20_%20%E5%A6%82%E4%BD%95%E7%94%A8%E5%8D%8F%E7%A8%8B%E6%9D%A5%E4%BC%98%E5%8C%96%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%9A%E5%8A%A1%EF%BC%9F/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-极客时间/Java性能调优实战/18 _ 如何设置线程池大小？" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/18%20_%20%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%A4%A7%E5%B0%8F%EF%BC%9F/">极客时间/Java性能调优实战/18 _ 如何设置线程池大小？</a>
    </h1>
  

        
        <a href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/18%20_%20%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%A4%A7%E5%B0%8F%EF%BC%9F/" class="archive-article-date">
  	<time datetime="2024-02-27T16:31:56.115Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2024-02-28</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <audio title="18 _ 如何设置线程池大小？" src="https://static001.geekbang.org/resource/audio/46/04/4614247beaaad69c1f8c0735f5dfd904.mp3" controls="controls"></audio> 
<p>你好，我是刘超。</p><p>还记得我在16讲中说过“线程池的线程数量设置过多会导致线程竞争激烈”吗？今天再补一句，如果线程数量设置过少的话，还会导致系统无法充分利用计算机资源。那么如何设置才不会影响系统性能呢？</p><p>其实线程池的设置是有方法的，不是凭借简单的估算来决定的。<span class="orange">今天我们就来看看究竟有哪些计算方法可以复用，</span>线程池中各个参数之间又存在怎样的关系。</p><h2>线程池原理</h2><p>开始优化之前，我们先来看看线程池的实现原理，有助于你更好地理解后面的内容。</p><p>在HotSpot VM的线程模型中，Java线程被一对一映射为内核线程。Java在使用线程执行程序时，需要创建一个内核线程；当该Java线程被终止时，这个内核线程也会被回收。因此Java线程的创建与销毁将会消耗一定的计算机资源，从而增加系统的性能开销。</p><p>除此之外，大量创建线程同样会给系统带来性能问题，因为内存和CPU资源都将被线程抢占，如果处理不当，就会发生内存溢出、CPU使用率超负荷等问题。</p><p>为了解决上述两类问题，Java提供了线程池概念，对于频繁创建线程的业务场景，线程池可以创建固定的线程数量，并且在操作系统底层，轻量级进程将会把这些线程映射到内核。</p><p><span class="orange">线程池可以提高线程复用，又可以固定最大线程使用量，防止无限制地创建线程。</span>当程序提交一个任务需要一个线程时，会去线程池中查找是否有空闲的线程，若有，则直接使用线程池中的线程工作，若没有，会去判断当前已创建的线程数量是否超过最大线程数量，如未超过，则创建新线程，如已超过，则进行排队等待或者直接抛出异常。</p><!-- [[[read_end]]] --><h2>线程池框架Executor</h2><p>Java最开始提供了ThreadPool实现了线程池，为了更好地实现用户级的线程调度，更有效地帮助开发人员进行多线程开发，Java提供了一套Executor框架。</p><p>这个框架中包括了ScheduledThreadPoolExecutor和ThreadPoolExecutor两个核心线程池。前者是用来定时执行任务，后者是用来执行被提交的任务。鉴于这两个线程池的核心原理是一样的，下面我们就重点看看ThreadPoolExecutor类是如何实现线程池的。</p><p>Executors实现了以下四种类型的ThreadPoolExecutor：</p><p><img src="https://static001.geekbang.org/resource/image/8d/69/8d3c1654add00cb63645f1332e4eb669.jpg?wh=1240*486" alt=""></p><p>Executors利用工厂模式实现的四种线程池，我们在使用的时候需要结合生产环境下的实际场景。不过我不太推荐使用它们，因为选择使用Executors提供的工厂类，将会忽略很多线程池的参数设置，工厂类一旦选择设置默认参数，就很容易导致无法调优参数设置，从而产生性能问题或者资源浪费。</p><p>这里<span class="orange">我建议你使用ThreadPoolExecutor自我定制一套线程池</span>。进入四种工厂类后，我们可以发现除了newScheduledThreadPool类，其它类均使用了ThreadPoolExecutor类进行实现，你可以通过以下代码简单看下该方法：</p><pre><code>    public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                              int maximumPoolSize,//线程池的最大线程数
                              long keepAliveTime,//当线程数大于核心线程数时，多余的空闲线程存活的最长时间
                              TimeUnit unit,//时间单位
                              BlockingQueue&lt;Runnable&gt; workQueue,//任务队列，用来储存等待执行任务的队列
                              ThreadFactory threadFactory,//线程工厂，用来创建线程，一般默认即可
                              RejectedExecutionHandler handler) //拒绝策略，当提交的任务过多而不能及时处理时，我们可以定制策略来处理任务
</code></pre><p>我们还可以通过下面这张图来了解下线程池中各个参数的相互关系：</p><p><img src="https://static001.geekbang.org/resource/image/66/e6/667149d07c494c29a23c1c08b9c0dbe6.jpg?wh=1666*1042" alt=""></p><p>通过上图，我们发现线程池有两个线程数的设置，一个为核心线程数，一个为最大线程数。在创建完线程池之后，默认情况下，线程池中并没有任何线程，等到有任务来才创建线程去执行任务。</p><p>但有一种情况排除在外，就是调用prestartAllCoreThreads()或者prestartCoreThread()方法的话，可以提前创建等于核心线程数的线程数量，这种方式被称为预热，在抢购系统中就经常被用到。</p><p>当创建的线程数等于 corePoolSize 时，提交的任务会被加入到设置的阻塞队列中。当队列满了，会创建线程执行任务，直到线程池中的数量等于maximumPoolSize。</p><p>当线程数量已经等于maximumPoolSize时， 新提交的任务无法加入到等待队列，也无法创建非核心线程直接执行，我们又没有为线程池设置拒绝策略，这时线程池就会抛出RejectedExecutionException异常，即线程池拒绝接受这个任务。</p><p>当线程池中创建的线程数量超过设置的corePoolSize，在某些线程处理完任务后，如果等待keepAliveTime时间后仍然没有新的任务分配给它，那么这个线程将会被回收。线程池回收线程时，会对所谓的“核心线程”和“非核心线程”一视同仁，直到线程池中线程的数量等于设置的corePoolSize参数，回收过程才会停止。</p><p>即使是corePoolSize线程，在一些非核心业务的线程池中，如果长时间地占用线程数量，也可能会影响到核心业务的线程池，这个时候就需要把没有分配任务的线程回收掉。</p><p>我们可以通过allowCoreThreadTimeOut设置项要求线程池：将包括“核心线程”在内的，没有任务分配的所有线程，在等待keepAliveTime时间后全部回收掉。</p><p>我们可以通过下面这张图来了解下线程池的线程分配流程：</p><p><img src="https://static001.geekbang.org/resource/image/b1/b0/b13aa36ef3b15a98fab1755ac36101b0.jpg?wh=746*950" alt=""></p><h2>计算线程数量</h2><p>了解完线程池的实现原理和框架，我们就可以动手实践优化线程池的设置了。</p><p>我们知道，环境具有多变性，设置一个绝对精准的线程数其实是不大可能的，但我们可以通过一些实际操作因素来计算出一个合理的线程数，避免由于线程池设置不合理而导致的性能问题。下面我们就来看看具体的计算方法。</p><p>一般多线程执行的任务类型可以分为CPU密集型和I/O密集型，根据不同的任务类型，我们计算线程数的方法也不一样。</p><p><strong>CPU密集型任务：</strong>这种任务消耗的主要是CPU资源，可以将线程数设置为N（CPU核心数）+1，比CPU核心数多出来的一个线程是为了防止线程偶发的缺页中断，或者其它原因导致的任务暂停而带来的影响。一旦任务暂停，CPU就会处于空闲状态，而在这种情况下多出来的一个线程就可以充分利用CPU的空闲时间。</p><p>下面我们用一个例子来验证下这个方法的可行性，通过观察CPU密集型任务在不同线程数下的性能情况就可以得出结果，你可以点击<a href="https://github.com/nickliuchao/threadpollsizetest" target="_blank" rel="noopener">Github</a>下载到本地运行测试：</p><pre><code>public class CPUTypeTest implements Runnable {

	//整体执行时间，包括在队列中等待的时间
	List&lt;Long&gt; wholeTimeList;
	//真正执行时间
	List&lt;Long&gt; runTimeList;
	
	private long initStartTime = 0;
	
	/**
	 * 构造函数
	 * @param runTimeList
	 * @param wholeTimeList
	 */
	public CPUTypeTest(List&lt;Long&gt; runTimeList, List&lt;Long&gt; wholeTimeList) {
		initStartTime = System.currentTimeMillis();
		this.runTimeList = runTimeList;
		this.wholeTimeList = wholeTimeList;
	}
	
	/**
	 * 判断素数
	 * @param number
	 * @return
	 */
	public boolean isPrime(final int number) {
		if (number &lt;= 1)
			return false;


		for (int i = 2; i &lt;= Math.sqrt(number); i++) {
			if (number % i == 0)
				return false;
		}
		return true;
	}

	/**
	 * 計算素数
	 * @param number
	 * @return
	 */
	public int countPrimes(final int lower, final int upper) {
		int total = 0;
		for (int i = lower; i &lt;= upper; i++) {
			if (isPrime(i))
				total++;
		}
		return total;
	}

	public void run() {
		long start = System.currentTimeMillis();
		countPrimes(1, 1000000);
		long end = System.currentTimeMillis();


		long wholeTime = end - initStartTime;
		long runTime = end - start;
		wholeTimeList.add(wholeTime);
		runTimeList.add(runTime);
		System.out.println(&quot;单个线程花费时间：&quot; + (end - start));
	}
}
</code></pre><p>测试代码在4核 intel i5 CPU机器上的运行时间变化如下：</p><p><img src="https://static001.geekbang.org/resource/image/c5/f9/c54224d1ed2d579a550650693c70e4f9.jpg?wh=1628*924" alt=""></p><p>综上可知：当线程数量太小，同一时间大量请求将被阻塞在线程队列中排队等待执行线程，此时CPU没有得到充分利用；当线程数量太大，被创建的执行线程同时在争取CPU资源，又会导致大量的上下文切换，从而增加线程的执行时间，影响了整体执行效率。通过测试可知，4~6个线程数是最合适的。</p><p><strong>I/O密集型任务：</strong>这种任务应用起来，系统会用大部分的时间来处理I/O交互，而线程在处理I/O的时间段内不会占用CPU来处理，这时就可以将CPU交出给其它线程使用。因此在I/O密集型任务的应用中，我们可以多配置一些线程，具体的计算方法是2N。</p><p>这里我们还是通过一个例子来验证下这个公式是否可以标准化：</p><pre><code>public class IOTypeTest implements Runnable {

	//整体执行时间，包括在队列中等待的时间
	Vector&lt;Long&gt; wholeTimeList;
	//真正执行时间
	Vector&lt;Long&gt; runTimeList;
	
	private long initStartTime = 0;
	
	/**
	 * 构造函数
	 * @param runTimeList
	 * @param wholeTimeList
	 */
	public IOTypeTest(Vector&lt;Long&gt; runTimeList, Vector&lt;Long&gt; wholeTimeList) {
		initStartTime = System.currentTimeMillis();
		this.runTimeList = runTimeList;
		this.wholeTimeList = wholeTimeList;
	}
	
	/**
	 *IO操作
	 * @param number
	 * @return
	 * @throws IOException 
	 */
	public void readAndWrite() throws IOException {
		File sourceFile = new File(&quot;D:/test.txt&quot;);
        //创建输入流
        BufferedReader input = new BufferedReader(new FileReader(sourceFile));
        //读取源文件,写入到新的文件
        String line = null;
        while((line = input.readLine()) != null){
            //System.out.println(line);
        }
        //关闭输入输出流
        input.close();
	}

	public void run() {
		long start = System.currentTimeMillis();
		try {
			readAndWrite();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();


		long wholeTime = end - initStartTime;
		long runTime = end - start;
		wholeTimeList.add(wholeTime);
		runTimeList.add(runTime);
		System.out.println(&quot;单个线程花费时间：&quot; + (end - start));
	}
}
</code></pre><p>备注：由于测试代码读取2MB大小的文件，涉及到大内存，所以在运行之前，我们需要调整JVM的堆内存空间：-Xms4g -Xmx4g，避免发生频繁的FullGC，影响测试结果。</p><p><img src="https://static001.geekbang.org/resource/image/0b/88/0bb0fe79bc9fc3c386815e3d0bfcf088.jpg?wh=1572*848" alt=""></p><p>通过测试结果，我们可以看到每个线程所花费的时间。当线程数量在8时，线程平均执行时间是最佳的，这个线程数量和我们的计算公式所得的结果就差不多。</p><p>看完以上两种情况下的线程计算方法，你可能还想说，在平常的应用场景中，我们常常遇不到这两种极端情况，<strong>那么碰上一些常规的业务操作，比如，通过一个线程池实现向用户定时推送消息的业务，我们又该如何设置线程池的数量呢？</strong></p><p>此时我们可以参考以下公式来计算线程数：</p><pre><code>线程数=N（CPU核数）*（1+WT（线程等待时间）/ST（线程时间运行时间））
</code></pre><p>我们可以通过JDK自带的工具VisualVM来查看WT/ST比例，以下例子是基于运行纯CPU运算的例子，我们可以看到：</p><pre><code>WT（线程等待时间）= 36788ms [线程运行总时间] - 36788ms[ST（线程时间运行时间）]= 0
线程数=N（CPU核数）*（1+ 0 [WT（线程等待时间）]/36788ms[ST（线程时间运行时间）]）= N（CPU核数）
</code></pre><p>这跟我们之前通过CPU密集型的计算公式N+1所得出的结果差不多。</p><p><img src="https://static001.geekbang.org/resource/image/32/65/3214039ef8f15076084a363a9f0b0b65.jpg?wh=2372*1254" alt=""></p><p>综合来看，我们可以根据自己的业务场景，从“N+1”和“2N”两个公式中选出一个适合的，计算出一个大概的线程数量，之后通过实际压测，逐渐往“增大线程数量”和“减小线程数量”这两个方向调整，然后观察整体的处理时间变化，最终确定一个具体的线程数量。</p><h2>总结</h2><p>今天我们主要学习了线程池的实现原理，Java线程的创建和消耗会给系统带来性能开销，因此Java提供了线程池来复用线程，提高程序的并发效率。</p><p>Java通过用户线程与内核线程结合的1:1线程模型来实现，Java将线程的调度和管理设置在了用户态，提供了一套Executor框架来帮助开发人员提高效率。Executor框架不仅包括了线程池的管理，还提供了线程工厂、队列以及拒绝策略等，可以说Executor框架为并发编程提供了一个完善的架构体系。</p><p>在不同的业务场景以及不同配置的部署机器中，线程池的线程数量设置是不一样的。其设置不宜过大，也不宜过小，<span class="orange">要根据具体情况，计算出一个大概的数值，再通过实际的性能测试，计算出一个合理的线程数量。</span></p><p>我们要提高线程池的处理能力，一定要先保证一个合理的线程数量，也就是保证CPU处理线程的最大化。在此前提下，我们再增大线程池队列，通过队列将来不及处理的线程缓存起来。在设置缓存队列时，我们要尽量使用一个有界队列，以防因队列过大而导致的内存溢出问题。</p><h2>思考题</h2><p>在程序中，除了并行段代码，还有串行段代码。那么当程序同时存在串行和并行操作时，优化并行操作是不是优化系统的关键呢？</p><p>期待在留言区看到你的见解。也欢迎你点击“请朋友读”，把今天的内容分享给身边的朋友，邀请他一起讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/9c/35/9dc79371.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>你好旅行者</span>
  </div>
  <div class="_2_QraFYR_0">老师好！关于线程池我有一个问题一直不明白，在线程池达到了核心线程数，等待队列没满的这段时间，新的任务会被加入到等待队列。而当等待队列满了之后，最大线程数没满的这段时间，线程池会为新的任务直接创建线程。那岂不是说，我后来的任务反而比先到的任务更早被分配到线程的资源？这是不是有点不太合理呢？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的，如果队列满了，就会新增线程来执行任务，如果已经是最大线程数量，则会执行拒绝策略。<br><br>这里不应该说不合理，而是不公平。可以深入源码查看具体的实现。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-30 11:32:08</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/4d/49/28e73b9c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>明翼</span>
  </div>
  <div class="_2_QraFYR_0">老师早点发这个课就好了，先回答问题：程序的总体时间是由所有部分加起来时间决定的，串行如果很慢就会严重影响性能，优化是从性能最差的地方开始的。<br><br>请教问题：<br>1）按照老师的图，如果线程没超过核心线程，就创建，超过则加入到队列，队列满又没达到最大线程则创建非核心线程，那么创建好的线程是直接执行最近来的任务那，还是从队列的头部取一个执行。<br>2）第二个问题，如果线程池的线程数的在核心线程数量之内，A线程执行刚执行完任务，这时候来了个新来的任务a，那么这个A线程继续执行这个新来任务a，还是其他线程执行这个线程那，这里面有什么分配策略</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 1、如果队列满了，这个现成的任务会创建非核心线程，也就是不会先运行队列中的任务。<br>2、新来的任务a会通过创建新的线程来运行，只要线程数量小于核心线程数，新来的任务都会通过创建新的线程来运行。直到等于核心线程数，任务将会放到阻塞队列中，通过循环拿到阻塞队列中的任务执行。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-02 09:03:31</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/4e/1b/f4b786b9.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>飞翔</span>
  </div>
  <div class="_2_QraFYR_0">话说N+1和2N 是指的核心线程数嘛？ 那队列和最大线程数怎么设置呀</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 在一些非核心业务，我们可以将核心线程数设置小一些，最大线程数量设置为计算线程数量。在一些核心业务中，两者可以设置一样。阻塞队列可以根据具体业务场景设置，如果线程处理业务非常迅速，我们可以考虑将阻塞队列设置大一些，处理的请求吞吐量会大些；如果线程处理业务非常耗时，阻塞队列设置小些，防止请求在阻塞队列中等待过长时间而导致请求已超时。<br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-30 03:08:07</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/46/d1/a1ddf49f.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>阿杜</span>
  </div>
  <div class="_2_QraFYR_0">线程池核心线程数的设置多少不仅仅依赖cpu核数和执行时间，还有线程执行的资源，比如调用的db，db连接数有限，线程数太多就可能打满db连接。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 赞，做性能压测的时候会经常遇到这种情况，当某条SQL比较耗时的时候，如果线程数设置过大，就会出现不能打开DB连接的异常。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-12-17 16:33:08</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/Pr8laRQY3skrzzgen37ZIt4HQvtaThAcqvyK8eAzc9DRiak803q5HS7gCnXFxpx6CWibqT1Sic0h1TLMmVNUpJRibA/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>nico</span>
  </div>
  <div class="_2_QraFYR_0">老师，请教个问题，生产环境有些应用是混部的，即一个虚拟机上跑很多个java程序，这个时候估算一个程序中的线程池的线程数，是不是就不合理了？这个要怎么估算合理的线程池配置？还有就是即使是单实例部署，cpu资源是机器内共用的，不可能只分配给java线程，这个要怎么考虑？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 我们先考虑单个环境，再去调优复杂环境。大多情况下，重要的服务会单独部署，尽量减少重要业务的相互影响。如果是核心业务冗余在了一个服务上，建议拆分之后分别部署。<br><br>非核心业务，很多业务处理可能是在不同时间点，彼此相互不影响，所以不用过多考虑混合部署服务的情况。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-29 09:25:52</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/17/27/ec30d30a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Jxin</span>
  </div>
  <div class="_2_QraFYR_0">1.能理解io操作不占用cpu计算，但是io线程依旧不会让渡cpu资源的（执行时间片）。所以io线程池独立和调整io线程数只是因为它耗时不确定，一般耗时较长的特性。<br>2.综上所述，那么线程池线程数基本就20-30的样子（而且这个值怎么感觉是多个线程池的线程总数呢）。那么tomcat线程池默认好像200条吧，dubbo线程池我们设置1000条线程，这是否就不合理了？（线程这块太水，麻烦老师解惑下）</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 我们一般会调整tomcat的线程数量的，线上环境的tomcat线程数量我们一般是在16核CPU的环境下为20左右。如果有万级的并发过来，100以及1000的线程数量，可能会有问题。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-01 09:30:29</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/10/a6/4d2c933e.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>K</span>
  </div>
  <div class="_2_QraFYR_0">老师好，我也是看了下边同学的评论，又学到了很多知识。我有一个地方不理解，线程如果读文件的时候，这个线程可能处于wait的状态，然后cpu就可以被其他线程拿到使用了。那为什么看到有同学说：“io操作不占用cpu计算，但是io线程依旧不会让渡cpu资源的（执行时间片）”。麻烦老师解答一下，谢谢老师。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 这里理解是有误差的，目前很多服务器的IO操作都是交给DMA去完成，所以这里是让出CPU资源的。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-27 17:10:52</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/4d/fd/0aa0e39f.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>许童童</span>
  </div>
  <div class="_2_QraFYR_0">优化并行操作是不是优化系统的关键呢?<br>可以参考阿姆达尔定律<br>S=1&#47;(1-a+a&#47;n)<br>总之，优化并行操作带来的收益是有上限的。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 赞，Amdahl&#39;s定律指出优化串行是优化系统性能的关键，我们应该从算法入手，减少程序中串行的部分，而不是增加线程数来提高系统的并发处理能力。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-29 11:48:16</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/b3/c5/7fc124e2.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Liam</span>
  </div>
  <div class="_2_QraFYR_0">请教老师一个问题：<br><br>对于应用而言，可能有多种类型的任务要执行，我们是分别创建不同的线程池还是创建一个统一的线程池来控制资源的使用呢？<br><br>1 如果用一个统一的线程池，担心io任务占有太多线程导致其他任务没有足够的线程消费<br><br>2 如果用多个线程池，这个资源怎么管理，会不会导致整个应用的线程数量过多，引起太多上下文切换从而导致开销过大<br></div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 分别创建。<br><br>如果过分彼此相互影响，建议拆开服务，分别部署。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-29 08:40:12</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/9f/06/287d77dd.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>承香墨影</span>
  </div>
  <div class="_2_QraFYR_0">只有在 workQueue 满了之后才会创建新的线程，直到线程数到达 maximumPoolSize 值。所以这里的等待队列无法使用无界队列就会导致永远都用不上 maximumPoolSize。当我们自己指定 ThreadPoolExecutor 参数的时候，需要注意不要使用无界队列，或者使用无界队列让 corePoolSize 和 maximumPoolSize 保持一致也可以。<br>参考：newFixedThreadPool 的创建过程。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 赞</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-10-09 14:16:55</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/7b/57/a9b04544.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>QQ怪</span>
  </div>
  <div class="_2_QraFYR_0">老师，我想问下生产环境情况下很少碰到单个java进程在一台主机中运行，大部分肯定是多个进程同时运行，不如docker技术，都是共享同一套硬件，那这套计算方程式是不是不适用了？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 适用的，多个进程大部分时间不一定是重合运行的。但具体情况需要具体定，所以最终还是以压测调出来的线程数为准。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-29 12:10:03</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/66/c4/e87a61ec.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>这就是编程</span>
  </div>
  <div class="_2_QraFYR_0">看了，这个文章，发现用的阻塞队列，为什么要把任务加到队列后再去创建线程，为什么当没有达到最大线程可以直接创建。上网找了下，发现Tomcat中有一个StandardThreadExecutor线程池，该线程池execute执行策略是优先扩充线程到maximumPoolSize，再offer到queue，如果满了就reject。</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-03-11 15:42:53</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/14/c1/38/e45bbb6b.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>再续啸傲</span>
  </div>
  <div class="_2_QraFYR_0">老师，我在本地测试的时候，随着核心线程数的增加，表现出线程平均执行时间增长很快，但是线程整体实行时间却一直在下降。这是否说明任务在等待队列的等待时间要远大于线程之间上下文切换所花费的时间？<br>如果要将tomcat线程数设置成两位数，是否对服务TPS有较大影响。当万级请求进来的时候正常请求被阻塞，导致前端页面显示请求超时？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 是的，根据当前机器的CPU数量和执行的具体业务来定，如果是纯CPU执行的任务，一般是跟CPU数量的核心线程数量会最大效率利用CPU，而如果是I&#47;O型业务，本身处理I&#47;O的时间是不占用CPU的，可以适当将核心线程数调大一些。如果核心线程数远远大于CPU的核数，整体执行时间也会增加。<br><br>Tomcat线程数需要根据自己的业务和机器的环境来定，正常业务一般比机器的CPU核数大一些即可。当万级并发请求时，如果是不保持长连接的情况下，可以适当调大acceptCount参数。当然，业务处理时间过长，在大并发量出现，由于tomcat无法及时处理，会导致504或其他错误。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-09 11:50:26</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/49/ef/02401473.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>月迷津渡</span>
  </div>
  <div class="_2_QraFYR_0">当创建的线程数等于 corePoolSize 时,提交的任务会被加入到设置的阻塞队列中,当队列满了，会创建线程执行任务，直到线程池中的数量等于 maximumPoolSize。<br>这句话颠覆我原来认知了。。难道不是要达到maxPoolSize再进队列么。。如果按上面这么说如果队列不满，就不会创建新线程执行它？就是我永远霸占了coreSize的线程处理，然后队列里有一个待处理，永远执行不了？求教</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 按照源码来说，是先判断工作线程是否小于corePoolSize ，如果是，则直接获取线程执行代码，否则，是先进入阻塞队列中。<br><br>按照源码看，如果核心线程被占用了，队列中的请求会一直处于等待状态。<br><br>建议阅读源码和手动实践一下。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-08-28 14:44:38</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/9c/f4/7e14ff8a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Jerry</span>
  </div>
  <div class="_2_QraFYR_0">老师，请教一下，在我的业务环境下一个24 core的node上，一个java进程有一万多个线程，其中有220个是runnable的，其他要么处于BLOCKED 要么处于parking&#47;waiting状态，这种情况应该怎么优化？ 始终处于parking&#47;waiting状态的thread是不会参与调度的，不影响调度效率，这样理解对吗？<br><br>      3    java.lang.Thread.State: BLOCKED (on object monitor)<br>    220    java.lang.Thread.State: RUNNABLE<br>     45    java.lang.Thread.State: TIMED_WAITING (on object monitor)<br>   6412    java.lang.Thread.State: TIMED_WAITING (parking)<br>    200    java.lang.Thread.State: TIMED_WAITING (sleeping)<br>      4    java.lang.Thread.State: WAITING (on object monitor)<br>   5292    java.lang.Thread.State: WAITING (parking)</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 处于等待状态的线程不会争夺CPU资源，但会占用其他系统资源。建议减少创建线程数量。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-07-29 15:56:44</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/e9/0b/1171ac71.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>WL</span>
  </div>
  <div class="_2_QraFYR_0">有三个问题请教一下老师:<br>1. 守护线程也会映射到CPU的线程吗, 我还是没太理解在计算线程数量的时候为啥不考虑守护线程呢, 是因为守护线程运行状态是要么一直运行, 要么一直等待这样不会变化的情况所以不考虑吗? 为啥守护线程的状态一直不变化呢, 它们不会出让CPU吗?<br>2. 文章中讲的主要是核心线程的数量如何配置, 最大线程数量和阻塞队列长度应该如何确定, 是随便配一下就行影响不大吗, 还是也有什么指导原则?<br>3. 想问一下老师在visualVM中的线程查看的视窗中线程状态等待, 休眠, 驻留, 监视这几个状态都是啥意思, 是啥情况下会变成这几个状态.<br></div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 1、守护线程也是JVM创建的线程，这块我没有具体看到源码。我们一般不会使用守护线程处理业务，我们这里讨论的是处理业务的线程数量。<br>2、在一些非核心业务，我们可以将核心线程数设置小一些，最大线程数量设置为计算线程数量。在一些核心业务中，两者可以设置一样。阻塞队列可以根据具体业务场景设置，如果线程处理业务非常迅速，我们可以考虑将阻塞队列设置大一些，处理的请求吞吐量会大些；如果线程处理业务非常耗时，阻塞队列设置小些，防止请求在阻塞队列中等待过长时间而导致请求已超时。<br><br>3、休眠对应的sleep操作，等待对应的wait，驻留对应的线程池里的空闲线程，监视对应的synchronized阻塞</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-29 16:37:04</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>姜涛</span>
  </div>
  <div class="_2_QraFYR_0">接口之间的http调用是IO密集型任务嘛？如果一个服务既有IO密集型任务也有CPU密集型任务，是不是线程池需要分别设置线程池大小？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 如果共用线程池，如果是IO密集型任务多一些，则可以按照IO密集型任务来设置，如果是CPU密集型任务多一些，可以取中间值。<br><br>做好线程隔离，则分别使用不同的线程池，可以分别设置线程数量。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-04-13 18:24:42</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJw7Dl3icqCVEicDFiaZ7tNtuXwNVDuboyID32R6hhLJeo27DNUaoILNtto1thYqMYWrrGCC0ujDeqBA/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>17702158422</span>
  </div>
  <div class="_2_QraFYR_0">老师请问下一般服务器下所有进程的线程至少100多个了，包括JAVA应用下也不止线程池的线程，还有垃圾回收线程，TOMCAT内置线程等等，这些线程都跑在几个核上，他们也存在上下文切换呀，改如何计算自定义线程池的数量呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 以上计算线程池大小都是基于一个比较理想状态下计算的。你说的这些线上环境只是一个微小的影响因素，实际在运行时，同时与线程池竞争资源的线程不会对这个公式造成非常大的影响。这些线程对CPU的使用率一般不会超过20%，系统负载也在1.0以下。<br><br>如果线上环境已经对线程池的大小产生了干扰，也就是说同时存在多线程池同时运行，且竞争CPU资源非常激烈，这个时候应该模拟线上环境，通过性能测试来调优线程池的线程数量。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-30 15:45:09</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/24/5d/65e61dcb.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>学无涯</span>
  </div>
  <div class="_2_QraFYR_0">老师，程序很快就跑完了，visualvm还没打开程序呢，我看你的图监视的是已终止的程序，那怎么显示已终止程序的信息的呢</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 用过debug模式，在main函数第一条语句设置一个断点，进入断点后，再去VisualVM操作CPU采样，之后再运行程序。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-29 11:00:51</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83eotSSnZic41tGkbflx0ogIg3ia6g2muFY1hCgosL2t3icZm7I8Ax1hcv1jNgr6vrZ53dpBuGhaoc6DKg/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>张学磊</span>
  </div>
  <div class="_2_QraFYR_0">老师，线程池流程图提交任务后的第一个节点应该是线程数是否大于核心线程数，如果是再判断队列是否已满，否则直接创建新线程。<br><br>思考题，个人觉得线性执行的代码会成为影响性能的关键，应尽量减少执行时间，比如减少锁持有时间，这样才能达到最大程度的并发。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 感谢学磊童鞋的提醒，已修正。<br><br>答案正确！</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-29 08:16:03</div>
  </div>
</div>
</div>
</li>
</ul>
      

      
    </div>
    <div class="article-info article-info-index">
      
      
      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/blog/2024/02/28/%E6%9E%81%E5%AE%A2%E6%97%B6%E9%97%B4/Java%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/18%20_%20%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%A4%A7%E5%B0%8F%EF%BC%9F/">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" target="_blank" rel="noopener" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
  
    <nav id="page-nav">
      <a class="extend prev" rel="prev" href="/blog/page/2/">&amp;laquo; Prev</a><a class="page-number" href="/blog/">1</a><a class="page-number" href="/blog/page/2/">2</a><span class="page-number current">3</span><a class="page-number" href="/blog/page/4/">4</a><a class="page-number" href="/blog/page/5/">5</a><a class="page-number" href="/blog/page/6/">6</a><a class="extend next" rel="next" href="/blog/page/4/">Next &amp;raquo;</a>
    </nav>
  


          </div>
        </div>
      </div>
      <footer id="footer">
  <div class="outer">
    <div id="footer-info">
    	<div class="footer-left">
    		&copy; 2024 zhangxu
    	</div>
      	<div class="footer-right">
      		<a href="http://hexo.io/" target="_blank">Hexo</a>  Theme <a href="https://github.com/litten/hexo-theme-yilia" target="_blank">Yilia</a> by Litten
      	</div>
    </div>
  </div>
</footer>
    </div>
    <script>
	var yiliaConfig = {
		mathjax: false,
		isHome: true,
		isPost: false,
		isArchive: false,
		isTag: false,
		isCategory: false,
		open_in_new: false,
		toc_hide_index: true,
		root: "/blog/",
		innerArchive: true,
		showTags: false
	}
</script>

<script>!function(t){function n(e){if(r[e])return r[e].exports;var i=r[e]={exports:{},id:e,loaded:!1};return t[e].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}var r={};n.m=t,n.c=r,n.p="./",n(0)}([function(t,n,r){r(195),t.exports=r(191)},function(t,n,r){var e=r(3),i=r(52),o=r(27),u=r(28),c=r(53),f="prototype",a=function(t,n,r){var s,l,h,v,p=t&a.F,d=t&a.G,y=t&a.S,g=t&a.P,b=t&a.B,m=d?e:y?e[n]||(e[n]={}):(e[n]||{})[f],x=d?i:i[n]||(i[n]={}),w=x[f]||(x[f]={});d&&(r=n);for(s in r)l=!p&&m&&void 0!==m[s],h=(l?m:r)[s],v=b&&l?c(h,e):g&&"function"==typeof h?c(Function.call,h):h,m&&u(m,s,h,t&a.U),x[s]!=h&&o(x,s,v),g&&w[s]!=h&&(w[s]=h)};e.core=i,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},function(t,n,r){var e=r(6);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n,r){var e=r(126)("wks"),i=r(76),o=r(3).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n,r){var e=r(94),i=r(33);t.exports=function(t){return e(i(t))}},function(t,n,r){t.exports=!r(4)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(2),i=r(167),o=r(50),u=Object.defineProperty;n.f=r(10)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){t.exports=!r(18)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(14),i=r(22);t.exports=r(12)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var e=r(20),i=r(58),o=r(42),u=Object.defineProperty;n.f=r(12)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){var e=r(40)("wks"),i=r(23),o=r(5).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n,r){var e=r(67),i=Math.min;t.exports=function(t){return t>0?i(e(t),9007199254740991):0}},function(t,n,r){var e=r(46);t.exports=function(t){return Object(e(t))}},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n,r){var e=r(63),i=r(34);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n,r){var e=r(21);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n){var r=t.exports={version:"2.4.0"};"number"==typeof __e&&(__e=r)},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,r){var e=r(11),i=r(66);t.exports=r(10)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var e=r(3),i=r(27),o=r(24),u=r(76)("src"),c="toString",f=Function[c],a=(""+f).split(c);r(52).inspectSource=function(t){return f.call(t)},(t.exports=function(t,n,r,c){var f="function"==typeof r;f&&(o(r,"name")||i(r,"name",n)),t[n]!==r&&(f&&(o(r,u)||i(r,u,t[n]?""+t[n]:a.join(String(n)))),t===e?t[n]=r:c?t[n]?t[n]=r:i(t,n,r):(delete t[n],i(t,n,r)))})(Function.prototype,c,function(){return"function"==typeof this&&this[u]||f.call(this)})},function(t,n,r){var e=r(1),i=r(4),o=r(46),u=function(t,n,r,e){var i=String(o(t)),u="<"+n;return""!==r&&(u+=" "+r+'="'+String(e).replace(/"/g,"&quot;")+'"'),u+">"+i+"</"+n+">"};t.exports=function(t,n){var r={};r[t]=n(u),e(e.P+e.F*i(function(){var n=""[t]('"');return n!==n.toLowerCase()||n.split('"').length>3}),"String",r)}},function(t,n,r){var e=r(115),i=r(46);t.exports=function(t){return e(i(t))}},function(t,n,r){var e=r(116),i=r(66),o=r(30),u=r(50),c=r(24),f=r(167),a=Object.getOwnPropertyDescriptor;n.f=r(10)?a:function(t,n){if(t=o(t),n=u(n,!0),f)try{return a(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(24),i=r(17),o=r(145)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on  "+t);return t}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n){t.exports={}},function(t,n){t.exports=!0},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(14).f,i=r(8),o=r(15)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var e=r(40)("keys"),i=r(23);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n,r){var e=r(5),i="__core-js_shared__",o=e[i]||(e[i]={});t.exports=function(t){return o[t]||(o[t]={})}},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},function(t,n,r){var e=r(21);t.exports=function(t,n){if(!e(t))return t;var r,i;if(n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;if("function"==typeof(r=t.valueOf)&&!e(i=r.call(t)))return i;if(!n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n,r){var e=r(5),i=r(25),o=r(36),u=r(44),c=r(14).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){n.f=r(15)},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on  "+t);return t}},function(t,n,r){var e=r(4);t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},function(t,n,r){var e=r(53),i=r(115),o=r(17),u=r(16),c=r(203);t.exports=function(t,n){var r=1==t,f=2==t,a=3==t,s=4==t,l=6==t,h=5==t||l,v=n||c;return function(n,c,p){for(var d,y,g=o(n),b=i(g),m=e(c,p,3),x=u(b.length),w=0,S=r?v(n,x):f?v(n,0):void 0;x>w;w++)if((h||w in b)&&(d=b[w],y=m(d,w,g),t))if(r)S[w]=y;else if(y)switch(t){case 3:return!0;case 5:return d;case 6:return w;case 2:S.push(d)}else if(s)return!1;return l?-1:a||s?s:S}}},function(t,n,r){var e=r(1),i=r(52),o=r(4);t.exports=function(t,n){var r=(i.Object||{})[t]||Object[t],u={};u[t]=n(r),e(e.S+e.F*o(function(){r(1)}),"Object",u)}},function(t,n,r){var e=r(6);t.exports=function(t,n){if(!e(t))return t;var r,i;if(n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;if("function"==typeof(r=t.valueOf)&&!e(i=r.call(t)))return i;if(!n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n,r){var e=r(5),i=r(25),o=r(91),u=r(13),c="prototype",f=function(t,n,r){var a,s,l,h=t&f.F,v=t&f.G,p=t&f.S,d=t&f.P,y=t&f.B,g=t&f.W,b=v?i:i[n]||(i[n]={}),m=b[c],x=v?e:p?e[n]:(e[n]||{})[c];v&&(r=n);for(a in r)(s=!h&&x&&void 0!==x[a])&&a in b||(l=s?x[a]:r[a],b[a]=v&&"function"!=typeof x[a]?r[a]:y&&s?o(l,e):g&&x[a]==l?function(t){var n=function(n,r,e){if(this instanceof t){switch(arguments.length){case 0:return new t;case 1:return new t(n);case 2:return new t(n,r)}return new t(n,r,e)}return t.apply(this,arguments)};return n[c]=t[c],n}(l):d&&"function"==typeof l?o(Function.call,l):l,d&&((b.virtual||(b.virtual={}))[a]=l,t&f.R&&m&&!m[a]&&u(m,a,l)))};f.F=1,f.G=2,f.S=4,f.P=8,f.B=16,f.W=32,f.U=64,f.R=128,t.exports=f},function(t,n){var r=t.exports={version:"2.4.0"};"number"==typeof __e&&(__e=r)},function(t,n,r){var e=r(26);t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,i){return t.call(n,r,e,i)}}return function(){return t.apply(n,arguments)}}},function(t,n,r){var e=r(183),i=r(1),o=r(126)("metadata"),u=o.store||(o.store=new(r(186))),c=function(t,n,r){var i=u.get(t);if(!i){if(!r)return;u.set(t,i=new e)}var o=i.get(n);if(!o){if(!r)return;i.set(n,o=new e)}return o},f=function(t,n,r){var e=c(n,r,!1);return void 0!==e&&e.has(t)},a=function(t,n,r){var e=c(n,r,!1);return void 0===e?void 0:e.get(t)},s=function(t,n,r,e){c(r,e,!0).set(t,n)},l=function(t,n){var r=c(t,n,!1),e=[];return r&&r.forEach(function(t,n){e.push(n)}),e},h=function(t){return void 0===t||"symbol"==typeof t?t:String(t)},v=function(t){i(i.S,"Reflect",t)};t.exports={store:u,map:c,has:f,get:a,set:s,keys:l,key:h,exp:v}},function(t,n,r){"use strict";if(r(10)){var e=r(69),i=r(3),o=r(4),u=r(1),c=r(127),f=r(152),a=r(53),s=r(68),l=r(66),h=r(27),v=r(73),p=r(67),d=r(16),y=r(75),g=r(50),b=r(24),m=r(180),x=r(114),w=r(6),S=r(17),_=r(137),O=r(70),E=r(32),P=r(71).f,j=r(154),F=r(76),M=r(7),A=r(48),N=r(117),T=r(146),I=r(155),k=r(80),L=r(123),R=r(74),C=r(130),D=r(160),U=r(11),W=r(31),G=U.f,B=W.f,V=i.RangeError,z=i.TypeError,q=i.Uint8Array,K="ArrayBuffer",J="Shared"+K,Y="BYTES_PER_ELEMENT",H="prototype",$=Array[H],X=f.ArrayBuffer,Q=f.DataView,Z=A(0),tt=A(2),nt=A(3),rt=A(4),et=A(5),it=A(6),ot=N(!0),ut=N(!1),ct=I.values,ft=I.keys,at=I.entries,st=$.lastIndexOf,lt=$.reduce,ht=$.reduceRight,vt=$.join,pt=$.sort,dt=$.slice,yt=$.toString,gt=$.toLocaleString,bt=M("iterator"),mt=M("toStringTag"),xt=F("typed_constructor"),wt=F("def_constructor"),St=c.CONSTR,_t=c.TYPED,Ot=c.VIEW,Et="Wrong length!",Pt=A(1,function(t,n){return Tt(T(t,t[wt]),n)}),jt=o(function(){return 1===new q(new Uint16Array([1]).buffer)[0]}),Ft=!!q&&!!q[H].set&&o(function(){new q(1).set({})}),Mt=function(t,n){if(void 0===t)throw z(Et);var r=+t,e=d(t);if(n&&!m(r,e))throw V(Et);return e},At=function(t,n){var r=p(t);if(r<0||r%n)throw V("Wrong offset!");return r},Nt=function(t){if(w(t)&&_t in t)return t;throw z(t+" is not a typed array!")},Tt=function(t,n){if(!(w(t)&&xt in t))throw z("It is not a typed array constructor!");return new t(n)},It=function(t,n){return kt(T(t,t[wt]),n)},kt=function(t,n){for(var r=0,e=n.length,i=Tt(t,e);e>r;)i[r]=n[r++];return i},Lt=function(t,n,r){G(t,n,{get:function(){return this._d[r]}})},Rt=function(t){var n,r,e,i,o,u,c=S(t),f=arguments.length,s=f>1?arguments[1]:void 0,l=void 0!==s,h=j(c);if(void 0!=h&&!_(h)){for(u=h.call(c),e=[],n=0;!(o=u.next()).done;n++)e.push(o.value);c=e}for(l&&f>2&&(s=a(s,arguments[2],2)),n=0,r=d(c.length),i=Tt(this,r);r>n;n++)i[n]=l?s(c[n],n):c[n];return i},Ct=function(){for(var t=0,n=arguments.length,r=Tt(this,n);n>t;)r[t]=arguments[t++];return r},Dt=!!q&&o(function(){gt.call(new q(1))}),Ut=function(){return gt.apply(Dt?dt.call(Nt(this)):Nt(this),arguments)},Wt={copyWithin:function(t,n){return D.call(Nt(this),t,n,arguments.length>2?arguments[2]:void 0)},every:function(t){return rt(Nt(this),t,arguments.length>1?arguments[1]:void 0)},fill:function(t){return C.apply(Nt(this),arguments)},filter:function(t){return It(this,tt(Nt(this),t,arguments.length>1?arguments[1]:void 0))},find:function(t){return et(Nt(this),t,arguments.length>1?arguments[1]:void 0)},findIndex:function(t){return it(Nt(this),t,arguments.length>1?arguments[1]:void 0)},forEach:function(t){Z(Nt(this),t,arguments.length>1?arguments[1]:void 0)},indexOf:function(t){return ut(Nt(this),t,arguments.length>1?arguments[1]:void 0)},includes:function(t){return ot(Nt(this),t,arguments.length>1?arguments[1]:void 0)},join:function(t){return vt.apply(Nt(this),arguments)},lastIndexOf:function(t){return st.apply(Nt(this),arguments)},map:function(t){return Pt(Nt(this),t,arguments.length>1?arguments[1]:void 0)},reduce:function(t){return lt.apply(Nt(this),arguments)},reduceRight:function(t){return ht.apply(Nt(this),arguments)},reverse:function(){for(var t,n=this,r=Nt(n).length,e=Math.floor(r/2),i=0;i<e;)t=n[i],n[i++]=n[--r],n[r]=t;return n},some:function(t){return nt(Nt(this),t,arguments.length>1?arguments[1]:void 0)},sort:function(t){return pt.call(Nt(this),t)},subarray:function(t,n){var r=Nt(this),e=r.length,i=y(t,e);return new(T(r,r[wt]))(r.buffer,r.byteOffset+i*r.BYTES_PER_ELEMENT,d((void 0===n?e:y(n,e))-i))}},Gt=function(t,n){return It(this,dt.call(Nt(this),t,n))},Bt=function(t){Nt(this);var n=At(arguments[1],1),r=this.length,e=S(t),i=d(e.length),o=0;if(i+n>r)throw V(Et);for(;o<i;)this[n+o]=e[o++]},Vt={entries:function(){return at.call(Nt(this))},keys:function(){return ft.call(Nt(this))},values:function(){return ct.call(Nt(this))}},zt=function(t,n){return w(t)&&t[_t]&&"symbol"!=typeof n&&n in t&&String(+n)==String(n)},qt=function(t,n){return zt(t,n=g(n,!0))?l(2,t[n]):B(t,n)},Kt=function(t,n,r){return!(zt(t,n=g(n,!0))&&w(r)&&b(r,"value"))||b(r,"get")||b(r,"set")||r.configurable||b(r,"writable")&&!r.writable||b(r,"enumerable")&&!r.enumerable?G(t,n,r):(t[n]=r.value,t)};St||(W.f=qt,U.f=Kt),u(u.S+u.F*!St,"Object",{getOwnPropertyDescriptor:qt,defineProperty:Kt}),o(function(){yt.call({})})&&(yt=gt=function(){return vt.call(this)});var Jt=v({},Wt);v(Jt,Vt),h(Jt,bt,Vt.values),v(Jt,{slice:Gt,set:Bt,constructor:function(){},toString:yt,toLocaleString:Ut}),Lt(Jt,"buffer","b"),Lt(Jt,"byteOffset","o"),Lt(Jt,"byteLength","l"),Lt(Jt,"length","e"),G(Jt,mt,{get:function(){return this[_t]}}),t.exports=function(t,n,r,f){f=!!f;var a=t+(f?"Clamped":"")+"Array",l="Uint8Array"!=a,v="get"+t,p="set"+t,y=i[a],g=y||{},b=y&&E(y),m=!y||!c.ABV,S={},_=y&&y[H],j=function(t,r){var e=t._d;return e.v[v](r*n+e.o,jt)},F=function(t,r,e){var i=t._d;f&&(e=(e=Math.round(e))<0?0:e>255?255:255&e),i.v[p](r*n+i.o,e,jt)},M=function(t,n){G(t,n,{get:function(){return j(this,n)},set:function(t){return F(this,n,t)},enumerable:!0})};m?(y=r(function(t,r,e,i){s(t,y,a,"_d");var o,u,c,f,l=0,v=0;if(w(r)){if(!(r instanceof X||(f=x(r))==K||f==J))return _t in r?kt(y,r):Rt.call(y,r);o=r,v=At(e,n);var p=r.byteLength;if(void 0===i){if(p%n)throw V(Et);if((u=p-v)<0)throw V(Et)}else if((u=d(i)*n)+v>p)throw V(Et);c=u/n}else c=Mt(r,!0),u=c*n,o=new X(u);for(h(t,"_d",{b:o,o:v,l:u,e:c,v:new Q(o)});l<c;)M(t,l++)}),_=y[H]=O(Jt),h(_,"constructor",y)):L(function(t){new y(null),new y(t)},!0)||(y=r(function(t,r,e,i){s(t,y,a);var o;return w(r)?r instanceof X||(o=x(r))==K||o==J?void 0!==i?new g(r,At(e,n),i):void 0!==e?new g(r,At(e,n)):new g(r):_t in r?kt(y,r):Rt.call(y,r):new g(Mt(r,l))}),Z(b!==Function.prototype?P(g).concat(P(b)):P(g),function(t){t in y||h(y,t,g[t])}),y[H]=_,e||(_.constructor=y));var A=_[bt],N=!!A&&("values"==A.name||void 0==A.name),T=Vt.values;h(y,xt,!0),h(_,_t,a),h(_,Ot,!0),h(_,wt,y),(f?new y(1)[mt]==a:mt in _)||G(_,mt,{get:function(){return a}}),S[a]=y,u(u.G+u.W+u.F*(y!=g),S),u(u.S,a,{BYTES_PER_ELEMENT:n,from:Rt,of:Ct}),Y in _||h(_,Y,n),u(u.P,a,Wt),R(a),u(u.P+u.F*Ft,a,{set:Bt}),u(u.P+u.F*!N,a,Vt),u(u.P+u.F*(_.toString!=yt),a,{toString:yt}),u(u.P+u.F*o(function(){new y(1).slice()}),a,{slice:Gt}),u(u.P+u.F*(o(function(){return[1,2].toLocaleString()!=new y([1,2]).toLocaleString()})||!o(function(){_.toLocaleString.call([1,2])})),a,{toLocaleString:Ut}),k[a]=N?A:T,e||N||h(_,bt,T)}}else t.exports=function(){}},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n,r){var e=r(21),i=r(5).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,r){t.exports=!r(12)&&!r(18)(function(){return 7!=Object.defineProperty(r(57)("div"),"a",{get:function(){return 7}}).a})},function(t,n,r){"use strict";var e=r(36),i=r(51),o=r(64),u=r(13),c=r(8),f=r(35),a=r(96),s=r(38),l=r(103),h=r(15)("iterator"),v=!([].keys&&"next"in[].keys()),p="keys",d="values",y=function(){return this};t.exports=function(t,n,r,g,b,m,x){a(r,n,g);var w,S,_,O=function(t){if(!v&&t in F)return F[t];switch(t){case p:case d:return function(){return new r(this,t)}}return function(){return new r(this,t)}},E=n+" Iterator",P=b==d,j=!1,F=t.prototype,M=F[h]||F["@@iterator"]||b&&F[b],A=M||O(b),N=b?P?O("entries"):A:void 0,T="Array"==n?F.entries||M:M;if(T&&(_=l(T.call(new t)))!==Object.prototype&&(s(_,E,!0),e||c(_,h)||u(_,h,y)),P&&M&&M.name!==d&&(j=!0,A=function(){return M.call(this)}),e&&!x||!v&&!j&&F[h]||u(F,h,A),f[n]=A,f[E]=y,b)if(w={values:P?A:O(d),keys:m?A:O(p),entries:N},x)for(S in w)S in F||o(F,S,w[S]);else i(i.P+i.F*(v||j),n,w);return w}},function(t,n,r){var e=r(20),i=r(100),o=r(34),u=r(39)("IE_PROTO"),c=function(){},f="prototype",a=function(){var t,n=r(57)("iframe"),e=o.length;for(n.style.display="none",r(93).appendChild(n),n.src="javascript:",t=n.contentWindow.document,t.open(),t.write("<script>document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a[f][o[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c[f]=e(t),r=new c,c[f]=null,r[u]=t):r=a(),void 0===n?r:i(r,n)}},function(t,n,r){var e=r(63),i=r(34).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){var e=r(8),i=r(9),o=r(90)(!1),u=r(39)("IE_PROTO");t.exports=function(t,n){var r,c=i(t),f=0,a=[];for(r in c)r!=u&&e(c,r)&&a.push(r);for(;n.length>f;)e(c,r=n[f++])&&(~o(a,r)||a.push(r));return a}},function(t,n,r){t.exports=r(13)},function(t,n,r){var e=r(76)("meta"),i=r(6),o=r(24),u=r(11).f,c=0,f=Object.isExtensible||function(){return!0},a=!r(4)(function(){return f(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!f(t))return"F";if(!n)return"E";s(t)}return t[e].i},h=function(t,n){if(!o(t,e)){if(!f(t))return!0;if(!n)return!1;s(t)}return t[e].w},v=function(t){return a&&p.NEED&&f(t)&&!o(t,e)&&s(t),t},p=t.exports={KEY:e,NEED:!1,fastKey:l,getWeak:h,onFreeze:v}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},function(t,n){t.exports=!1},function(t,n,r){var e=r(2),i=r(173),o=r(133),u=r(145)("IE_PROTO"),c=function(){},f="prototype",a=function(){var t,n=r(132)("iframe"),e=o.length;for(n.style.display="none",r(135).appendChild(n),n.src="javascript:",t=n.contentWindow.document,t.open(),t.write("<script>document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a[f][o[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c[f]=e(t),r=new c,c[f]=null,r[u]=t):r=a(),void 0===n?r:i(r,n)}},function(t,n,r){var e=r(175),i=r(133).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n,r){var e=r(175),i=r(133);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n,r){var e=r(28);t.exports=function(t,n,r){for(var i in n)e(t,i,n[i],r);return t}},function(t,n,r){"use strict";var e=r(3),i=r(11),o=r(10),u=r(7)("species");t.exports=function(t){var n=e[t];o&&n&&!n[u]&&i.f(n,u,{configurable:!0,get:function(){return this}})}},function(t,n,r){var e=r(67),i=Math.max,o=Math.min;t.exports=function(t,n){return t=e(t),t<0?i(t+n,0):o(t,n)}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n,r){var e=r(33);t.exports=function(t){return Object(e(t))}},function(t,n,r){var e=r(7)("unscopables"),i=Array.prototype;void 0==i[e]&&r(27)(i,e,{}),t.exports=function(t){i[e][t]=!0}},function(t,n,r){var e=r(53),i=r(169),o=r(137),u=r(2),c=r(16),f=r(154),a={},s={},n=t.exports=function(t,n,r,l,h){var v,p,d,y,g=h?function(){return t}:f(t),b=e(r,l,n?2:1),m=0;if("function"!=typeof g)throw TypeError(t+" is not iterable!");if(o(g)){for(v=c(t.length);v>m;m++)if((y=n?b(u(p=t[m])[0],p[1]):b(t[m]))===a||y===s)return y}else for(d=g.call(t);!(p=d.next()).done;)if((y=i(d,b,p.value,n))===a||y===s)return y};n.BREAK=a,n.RETURN=s},function(t,n){t.exports={}},function(t,n,r){var e=r(11).f,i=r(24),o=r(7)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var e=r(1),i=r(46),o=r(4),u=r(150),c="["+u+"]",f="​",a=RegExp("^"+c+c+"*"),s=RegExp(c+c+"*$"),l=function(t,n,r){var i={},c=o(function(){return!!u[t]()||f[t]()!=f}),a=i[t]=c?n(h):u[t];r&&(i[r]=a),e(e.P+e.F*c,"String",i)},h=l.trim=function(t,n){return t=String(i(t)),1&n&&(t=t.replace(a,"")),2&n&&(t=t.replace(s,"")),t};t.exports=l},function(t,n,r){t.exports={default:r(86),__esModule:!0}},function(t,n,r){t.exports={default:r(87),__esModule:!0}},function(t,n,r){"use strict";function e(t){return t&&t.__esModule?t:{default:t}}n.__esModule=!0;var i=r(84),o=e(i),u=r(83),c=e(u),f="function"==typeof c.default&&"symbol"==typeof o.default?function(t){return typeof t}:function(t){return t&&"function"==typeof c.default&&t.constructor===c.default&&t!==c.default.prototype?"symbol":typeof t};n.default="function"==typeof c.default&&"symbol"===f(o.default)?function(t){return void 0===t?"undefined":f(t)}:function(t){return t&&"function"==typeof c.default&&t.constructor===c.default&&t!==c.default.prototype?"symbol":void 0===t?"undefined":f(t)}},function(t,n,r){r(110),r(108),r(111),r(112),t.exports=r(25).Symbol},function(t,n,r){r(109),r(113),t.exports=r(44).f("iterator")},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n){t.exports=function(){}},function(t,n,r){var e=r(9),i=r(106),o=r(105);t.exports=function(t){return function(n,r,u){var c,f=e(n),a=i(f.length),s=o(u,a);if(t&&r!=r){for(;a>s;)if((c=f[s++])!=c)return!0}else for(;a>s;s++)if((t||s in f)&&f[s]===r)return t||s||0;return!t&&-1}}},function(t,n,r){var e=r(88);t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,i){return t.call(n,r,e,i)}}return function(){return t.apply(n,arguments)}}},function(t,n,r){var e=r(19),i=r(62),o=r(37);t.exports=function(t){var n=e(t),r=i.f;if(r)for(var u,c=r(t),f=o.f,a=0;c.length>a;)f.call(t,u=c[a++])&&n.push(u);return n}},function(t,n,r){t.exports=r(5).document&&document.documentElement},function(t,n,r){var e=r(56);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n,r){var e=r(56);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){"use strict";var e=r(60),i=r(22),o=r(38),u={};r(13)(u,r(15)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n,r){var e=r(19),i=r(9);t.exports=function(t,n){for(var r,o=i(t),u=e(o),c=u.length,f=0;c>f;)if(o[r=u[f++]]===n)return r}},function(t,n,r){var e=r(23)("meta"),i=r(21),o=r(8),u=r(14).f,c=0,f=Object.isExtensible||function(){return!0},a=!r(18)(function(){return f(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!f(t))return"F";if(!n)return"E";s(t)}return t[e].i},h=function(t,n){if(!o(t,e)){if(!f(t))return!0;if(!n)return!1;s(t)}return t[e].w},v=function(t){return a&&p.NEED&&f(t)&&!o(t,e)&&s(t),t},p=t.exports={KEY:e,NEED:!1,fastKey:l,getWeak:h,onFreeze:v}},function(t,n,r){var e=r(14),i=r(20),o=r(19);t.exports=r(12)?Object.defineProperties:function(t,n){i(t);for(var r,u=o(n),c=u.length,f=0;c>f;)e.f(t,r=u[f++],n[r]);return t}},function(t,n,r){var e=r(37),i=r(22),o=r(9),u=r(42),c=r(8),f=r(58),a=Object.getOwnPropertyDescriptor;n.f=r(12)?a:function(t,n){if(t=o(t),n=u(n,!0),f)try{return a(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(9),i=r(61).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],c=function(t){try{return i(t)}catch(t){return u.slice()}};t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?c(t):i(e(t))}},function(t,n,r){var e=r(8),i=r(77),o=r(39)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n,r){var e=r(41),i=r(33);t.exports=function(t){return function(n,r){var o,u,c=String(i(n)),f=e(r),a=c.length;return f<0||f>=a?t?"":void 0:(o=c.charCodeAt(f),o<55296||o>56319||f+1===a||(u=c.charCodeAt(f+1))<56320||u>57343?t?c.charAt(f):o:t?c.slice(f,f+2):u-56320+(o-55296<<10)+65536)}}},function(t,n,r){var e=r(41),i=Math.max,o=Math.min;t.exports=function(t,n){return t=e(t),t<0?i(t+n,0):o(t,n)}},function(t,n,r){var e=r(41),i=Math.min;t.exports=function(t){return t>0?i(e(t),9007199254740991):0}},function(t,n,r){"use strict";var e=r(89),i=r(97),o=r(35),u=r(9);t.exports=r(59)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):"keys"==n?i(0,r):"values"==n?i(0,t[r]):i(0,[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n){},function(t,n,r){"use strict";var e=r(104)(!0);r(59)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";var e=r(5),i=r(8),o=r(12),u=r(51),c=r(64),f=r(99).KEY,a=r(18),s=r(40),l=r(38),h=r(23),v=r(15),p=r(44),d=r(43),y=r(98),g=r(92),b=r(95),m=r(20),x=r(9),w=r(42),S=r(22),_=r(60),O=r(102),E=r(101),P=r(14),j=r(19),F=E.f,M=P.f,A=O.f,N=e.Symbol,T=e.JSON,I=T&&T.stringify,k="prototype",L=v("_hidden"),R=v("toPrimitive"),C={}.propertyIsEnumerable,D=s("symbol-registry"),U=s("symbols"),W=s("op-symbols"),G=Object[k],B="function"==typeof N,V=e.QObject,z=!V||!V[k]||!V[k].findChild,q=o&&a(function(){return 7!=_(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=F(G,n);e&&delete G[n],M(t,n,r),e&&t!==G&&M(G,n,e)}:M,K=function(t){var n=U[t]=_(N[k]);return n._k=t,n},J=B&&"symbol"==typeof N.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof N},Y=function(t,n,r){return t===G&&Y(W,n,r),m(t),n=w(n,!0),m(r),i(U,n)?(r.enumerable?(i(t,L)&&t[L][n]&&(t[L][n]=!1),r=_(r,{enumerable:S(0,!1)})):(i(t,L)||M(t,L,S(1,{})),t[L][n]=!0),q(t,n,r)):M(t,n,r)},H=function(t,n){m(t);for(var r,e=g(n=x(n)),i=0,o=e.length;o>i;)Y(t,r=e[i++],n[r]);return t},$=function(t,n){return void 0===n?_(t):H(_(t),n)},X=function(t){var n=C.call(this,t=w(t,!0));return!(this===G&&i(U,t)&&!i(W,t))&&(!(n||!i(this,t)||!i(U,t)||i(this,L)&&this[L][t])||n)},Q=function(t,n){if(t=x(t),n=w(n,!0),t!==G||!i(U,n)||i(W,n)){var r=F(t,n);return!r||!i(U,n)||i(t,L)&&t[L][n]||(r.enumerable=!0),r}},Z=function(t){for(var n,r=A(x(t)),e=[],o=0;r.length>o;)i(U,n=r[o++])||n==L||n==f||e.push(n);return e},tt=function(t){for(var n,r=t===G,e=A(r?W:x(t)),o=[],u=0;e.length>u;)!i(U,n=e[u++])||r&&!i(G,n)||o.push(U[n]);return o};B||(N=function(){if(this instanceof N)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===G&&n.call(W,r),i(this,L)&&i(this[L],t)&&(this[L][t]=!1),q(this,t,S(1,r))};return o&&z&&q(G,t,{configurable:!0,set:n}),K(t)},c(N[k],"toString",function(){return this._k}),E.f=Q,P.f=Y,r(61).f=O.f=Z,r(37).f=X,r(62).f=tt,o&&!r(36)&&c(G,"propertyIsEnumerable",X,!0),p.f=function(t){return K(v(t))}),u(u.G+u.W+u.F*!B,{Symbol:N});for(var nt="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),rt=0;nt.length>rt;)v(nt[rt++]);for(var nt=j(v.store),rt=0;nt.length>rt;)d(nt[rt++]);u(u.S+u.F*!B,"Symbol",{for:function(t){return i(D,t+="")?D[t]:D[t]=N(t)},keyFor:function(t){if(J(t))return y(D,t);throw TypeError(t+" is not a symbol!")},useSetter:function(){z=!0},useSimple:function(){z=!1}}),u(u.S+u.F*!B,"Object",{create:$,defineProperty:Y,defineProperties:H,getOwnPropertyDescriptor:Q,getOwnPropertyNames:Z,getOwnPropertySymbols:tt}),T&&u(u.S+u.F*(!B||a(function(){var t=N();return"[null]"!=I([t])||"{}"!=I({a:t})||"{}"!=I(Object(t))})),"JSON",{stringify:function(t){if(void 0!==t&&!J(t)){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);return n=e[1],"function"==typeof n&&(r=n),!r&&b(n)||(n=function(t,n){if(r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,I.apply(T,e)}}}),N[k][R]||r(13)(N[k],R,N[k].valueOf),l(N,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){r(43)("asyncIterator")},function(t,n,r){r(43)("observable")},function(t,n,r){r(107);for(var e=r(5),i=r(13),o=r(35),u=r(15)("toStringTag"),c=["NodeList","DOMTokenList","MediaList","StyleSheetList","CSSRuleList"],f=0;f<5;f++){var a=c[f],s=e[a],l=s&&s.prototype;l&&!l[u]&&i(l,u,a),o[a]=o.Array}},function(t,n,r){var e=r(45),i=r(7)("toStringTag"),o="Arguments"==e(function(){return arguments}()),u=function(t,n){try{return t[n]}catch(t){}};t.exports=function(t){var n,r,c;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=u(n=Object(t),i))?r:o?e(n):"Object"==(c=e(n))&&"function"==typeof n.callee?"Arguments":c}},function(t,n,r){var e=r(45);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(30),i=r(16),o=r(75);t.exports=function(t){return function(n,r,u){var c,f=e(n),a=i(f.length),s=o(u,a);if(t&&r!=r){for(;a>s;)if((c=f[s++])!=c)return!0}else for(;a>s;s++)if((t||s in f)&&f[s]===r)return t||s||0;return!t&&-1}}},function(t,n,r){"use strict";var e=r(3),i=r(1),o=r(28),u=r(73),c=r(65),f=r(79),a=r(68),s=r(6),l=r(4),h=r(123),v=r(81),p=r(136);t.exports=function(t,n,r,d,y,g){var b=e[t],m=b,x=y?"set":"add",w=m&&m.prototype,S={},_=function(t){var n=w[t];o(w,t,"delete"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"has"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"get"==t?function(t){return g&&!s(t)?void 0:n.call(this,0===t?0:t)}:"add"==t?function(t){return n.call(this,0===t?0:t),this}:function(t,r){return n.call(this,0===t?0:t,r),this})};if("function"==typeof m&&(g||w.forEach&&!l(function(){(new m).entries().next()}))){var O=new m,E=O[x](g?{}:-0,1)!=O,P=l(function(){O.has(1)}),j=h(function(t){new m(t)}),F=!g&&l(function(){for(var t=new m,n=5;n--;)t[x](n,n);return!t.has(-0)});j||(m=n(function(n,r){a(n,m,t);var e=p(new b,n,m);return void 0!=r&&f(r,y,e[x],e),e}),m.prototype=w,w.constructor=m),(P||F)&&(_("delete"),_("has"),y&&_("get")),(F||E)&&_(x),g&&w.clear&&delete w.clear}else m=d.getConstructor(n,t,y,x),u(m.prototype,r),c.NEED=!0;return v(m,t),S[t]=m,i(i.G+i.W+i.F*(m!=b),S),g||d.setStrong(m,t,y),m}},function(t,n,r){"use strict";var e=r(27),i=r(28),o=r(4),u=r(46),c=r(7);t.exports=function(t,n,r){var f=c(t),a=r(u,f,""[t]),s=a[0],l=a[1];o(function(){var n={};return n[f]=function(){return 7},7!=""[t](n)})&&(i(String.prototype,t,s),e(RegExp.prototype,f,2==n?function(t,n){return l.call(t,this,n)}:function(t){return l.call(t,this)}))}
},function(t,n,r){"use strict";var e=r(2);t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},function(t,n,r){var e=r(6),i=r(45),o=r(7)("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[o])?!!n:"RegExp"==i(t))}},function(t,n,r){var e=r(7)("iterator"),i=!1;try{var o=[7][e]();o.return=function(){i=!0},Array.from(o,function(){throw 2})}catch(t){}t.exports=function(t,n){if(!n&&!i)return!1;var r=!1;try{var o=[7],u=o[e]();u.next=function(){return{done:r=!0}},o[e]=function(){return u},t(o)}catch(t){}return r}},function(t,n,r){t.exports=r(69)||!r(4)(function(){var t=Math.random();__defineSetter__.call(null,t,function(){}),delete r(3)[t]})},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){var e=r(3),i="__core-js_shared__",o=e[i]||(e[i]={});t.exports=function(t){return o[t]||(o[t]={})}},function(t,n,r){for(var e,i=r(3),o=r(27),u=r(76),c=u("typed_array"),f=u("view"),a=!(!i.ArrayBuffer||!i.DataView),s=a,l=0,h="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");l<9;)(e=i[h[l++]])?(o(e.prototype,c,!0),o(e.prototype,f,!0)):s=!1;t.exports={ABV:a,CONSTR:s,TYPED:c,VIEW:f}},function(t,n){"use strict";var r={versions:function(){var t=window.navigator.userAgent;return{trident:t.indexOf("Trident")>-1,presto:t.indexOf("Presto")>-1,webKit:t.indexOf("AppleWebKit")>-1,gecko:t.indexOf("Gecko")>-1&&-1==t.indexOf("KHTML"),mobile:!!t.match(/AppleWebKit.*Mobile.*/),ios:!!t.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),android:t.indexOf("Android")>-1||t.indexOf("Linux")>-1,iPhone:t.indexOf("iPhone")>-1||t.indexOf("Mac")>-1,iPad:t.indexOf("iPad")>-1,webApp:-1==t.indexOf("Safari"),weixin:-1==t.indexOf("MicroMessenger")}}()};t.exports=r},function(t,n,r){"use strict";var e=r(85),i=function(t){return t&&t.__esModule?t:{default:t}}(e),o=function(){function t(t,n,e){return n||e?String.fromCharCode(n||e):r[t]||t}function n(t){return e[t]}var r={"&quot;":'"',"&lt;":"<","&gt;":">","&amp;":"&","&nbsp;":" "},e={};for(var u in r)e[r[u]]=u;return r["&apos;"]="'",e["'"]="&#39;",{encode:function(t){return t?(""+t).replace(/['<> "&]/g,n).replace(/\r?\n/g,"<br/>").replace(/\s/g,"&nbsp;"):""},decode:function(n){return n?(""+n).replace(/<br\s*\/?>/gi,"\n").replace(/&quot;|&lt;|&gt;|&amp;|&nbsp;|&apos;|&#(\d+);|&#(\d+)/g,t).replace(/\u00a0/g," "):""},encodeBase16:function(t){if(!t)return t;t+="";for(var n=[],r=0,e=t.length;e>r;r++)n.push(t.charCodeAt(r).toString(16).toUpperCase());return n.join("")},encodeBase16forJSON:function(t){if(!t)return t;t=t.replace(/[\u4E00-\u9FBF]/gi,function(t){return escape(t).replace("%u","\\u")});for(var n=[],r=0,e=t.length;e>r;r++)n.push(t.charCodeAt(r).toString(16).toUpperCase());return n.join("")},decodeBase16:function(t){if(!t)return t;t+="";for(var n=[],r=0,e=t.length;e>r;r+=2)n.push(String.fromCharCode("0x"+t.slice(r,r+2)));return n.join("")},encodeObject:function(t){if(t instanceof Array)for(var n=0,r=t.length;r>n;n++)t[n]=o.encodeObject(t[n]);else if("object"==(void 0===t?"undefined":(0,i.default)(t)))for(var e in t)t[e]=o.encodeObject(t[e]);else if("string"==typeof t)return o.encode(t);return t},loadScript:function(t){var n=document.createElement("script");document.getElementsByTagName("body")[0].appendChild(n),n.setAttribute("src",t)},addLoadEvent:function(t){var n=window.onload;"function"!=typeof window.onload?window.onload=t:window.onload=function(){n(),t()}}}}();t.exports=o},function(t,n,r){"use strict";var e=r(17),i=r(75),o=r(16);t.exports=function(t){for(var n=e(this),r=o(n.length),u=arguments.length,c=i(u>1?arguments[1]:void 0,r),f=u>2?arguments[2]:void 0,a=void 0===f?r:i(f,r);a>c;)n[c++]=t;return n}},function(t,n,r){"use strict";var e=r(11),i=r(66);t.exports=function(t,n,r){n in t?e.f(t,n,i(0,r)):t[n]=r}},function(t,n,r){var e=r(6),i=r(3).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n,r){var e=r(7)("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(r){try{return n[e]=!1,!"/./"[t](n)}catch(t){}}return!0}},function(t,n,r){t.exports=r(3).document&&document.documentElement},function(t,n,r){var e=r(6),i=r(144).set;t.exports=function(t,n,r){var o,u=n.constructor;return u!==r&&"function"==typeof u&&(o=u.prototype)!==r.prototype&&e(o)&&i&&i(t,o),t}},function(t,n,r){var e=r(80),i=r(7)("iterator"),o=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||o[i]===t)}},function(t,n,r){var e=r(45);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){"use strict";var e=r(70),i=r(66),o=r(81),u={};r(27)(u,r(7)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n,r){"use strict";var e=r(69),i=r(1),o=r(28),u=r(27),c=r(24),f=r(80),a=r(139),s=r(81),l=r(32),h=r(7)("iterator"),v=!([].keys&&"next"in[].keys()),p="keys",d="values",y=function(){return this};t.exports=function(t,n,r,g,b,m,x){a(r,n,g);var w,S,_,O=function(t){if(!v&&t in F)return F[t];switch(t){case p:case d:return function(){return new r(this,t)}}return function(){return new r(this,t)}},E=n+" Iterator",P=b==d,j=!1,F=t.prototype,M=F[h]||F["@@iterator"]||b&&F[b],A=M||O(b),N=b?P?O("entries"):A:void 0,T="Array"==n?F.entries||M:M;if(T&&(_=l(T.call(new t)))!==Object.prototype&&(s(_,E,!0),e||c(_,h)||u(_,h,y)),P&&M&&M.name!==d&&(j=!0,A=function(){return M.call(this)}),e&&!x||!v&&!j&&F[h]||u(F,h,A),f[n]=A,f[E]=y,b)if(w={values:P?A:O(d),keys:m?A:O(p),entries:N},x)for(S in w)S in F||o(F,S,w[S]);else i(i.P+i.F*(v||j),n,w);return w}},function(t,n){var r=Math.expm1;t.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:t>-1e-6&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},function(t,n,r){var e=r(3),i=r(151).set,o=e.MutationObserver||e.WebKitMutationObserver,u=e.process,c=e.Promise,f="process"==r(45)(u);t.exports=function(){var t,n,r,a=function(){var e,i;for(f&&(e=u.domain)&&e.exit();t;){i=t.fn,t=t.next;try{i()}catch(e){throw t?r():n=void 0,e}}n=void 0,e&&e.enter()};if(f)r=function(){u.nextTick(a)};else if(o){var s=!0,l=document.createTextNode("");new o(a).observe(l,{characterData:!0}),r=function(){l.data=s=!s}}else if(c&&c.resolve){var h=c.resolve();r=function(){h.then(a)}}else r=function(){i.call(e,a)};return function(e){var i={fn:e,next:void 0};n&&(n.next=i),t||(t=i,r()),n=i}}},function(t,n,r){var e=r(6),i=r(2),o=function(t,n){if(i(t),!e(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,e){try{e=r(53)(Function.call,r(31).f(Object.prototype,"__proto__").set,2),e(t,[]),n=!(t instanceof Array)}catch(t){n=!0}return function(t,r){return o(t,r),n?t.__proto__=r:e(t,r),t}}({},!1):void 0),check:o}},function(t,n,r){var e=r(126)("keys"),i=r(76);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n,r){var e=r(2),i=r(26),o=r(7)("species");t.exports=function(t,n){var r,u=e(t).constructor;return void 0===u||void 0==(r=e(u)[o])?n:i(r)}},function(t,n,r){var e=r(67),i=r(46);t.exports=function(t){return function(n,r){var o,u,c=String(i(n)),f=e(r),a=c.length;return f<0||f>=a?t?"":void 0:(o=c.charCodeAt(f),o<55296||o>56319||f+1===a||(u=c.charCodeAt(f+1))<56320||u>57343?t?c.charAt(f):o:t?c.slice(f,f+2):u-56320+(o-55296<<10)+65536)}}},function(t,n,r){var e=r(122),i=r(46);t.exports=function(t,n,r){if(e(n))throw TypeError("String#"+r+" doesn't accept regex!");return String(i(t))}},function(t,n,r){"use strict";var e=r(67),i=r(46);t.exports=function(t){var n=String(i(this)),r="",o=e(t);if(o<0||o==1/0)throw RangeError("Count can't be negative");for(;o>0;(o>>>=1)&&(n+=n))1&o&&(r+=n);return r}},function(t,n){t.exports="\t\n\v\f\r   ᠎             　\u2028\u2029\ufeff"},function(t,n,r){var e,i,o,u=r(53),c=r(121),f=r(135),a=r(132),s=r(3),l=s.process,h=s.setImmediate,v=s.clearImmediate,p=s.MessageChannel,d=0,y={},g="onreadystatechange",b=function(){var t=+this;if(y.hasOwnProperty(t)){var n=y[t];delete y[t],n()}},m=function(t){b.call(t.data)};h&&v||(h=function(t){for(var n=[],r=1;arguments.length>r;)n.push(arguments[r++]);return y[++d]=function(){c("function"==typeof t?t:Function(t),n)},e(d),d},v=function(t){delete y[t]},"process"==r(45)(l)?e=function(t){l.nextTick(u(b,t,1))}:p?(i=new p,o=i.port2,i.port1.onmessage=m,e=u(o.postMessage,o,1)):s.addEventListener&&"function"==typeof postMessage&&!s.importScripts?(e=function(t){s.postMessage(t+"","*")},s.addEventListener("message",m,!1)):e=g in a("script")?function(t){f.appendChild(a("script"))[g]=function(){f.removeChild(this),b.call(t)}}:function(t){setTimeout(u(b,t,1),0)}),t.exports={set:h,clear:v}},function(t,n,r){"use strict";var e=r(3),i=r(10),o=r(69),u=r(127),c=r(27),f=r(73),a=r(4),s=r(68),l=r(67),h=r(16),v=r(71).f,p=r(11).f,d=r(130),y=r(81),g="ArrayBuffer",b="DataView",m="prototype",x="Wrong length!",w="Wrong index!",S=e[g],_=e[b],O=e.Math,E=e.RangeError,P=e.Infinity,j=S,F=O.abs,M=O.pow,A=O.floor,N=O.log,T=O.LN2,I="buffer",k="byteLength",L="byteOffset",R=i?"_b":I,C=i?"_l":k,D=i?"_o":L,U=function(t,n,r){var e,i,o,u=Array(r),c=8*r-n-1,f=(1<<c)-1,a=f>>1,s=23===n?M(2,-24)-M(2,-77):0,l=0,h=t<0||0===t&&1/t<0?1:0;for(t=F(t),t!=t||t===P?(i=t!=t?1:0,e=f):(e=A(N(t)/T),t*(o=M(2,-e))<1&&(e--,o*=2),t+=e+a>=1?s/o:s*M(2,1-a),t*o>=2&&(e++,o/=2),e+a>=f?(i=0,e=f):e+a>=1?(i=(t*o-1)*M(2,n),e+=a):(i=t*M(2,a-1)*M(2,n),e=0));n>=8;u[l++]=255&i,i/=256,n-=8);for(e=e<<n|i,c+=n;c>0;u[l++]=255&e,e/=256,c-=8);return u[--l]|=128*h,u},W=function(t,n,r){var e,i=8*r-n-1,o=(1<<i)-1,u=o>>1,c=i-7,f=r-1,a=t[f--],s=127&a;for(a>>=7;c>0;s=256*s+t[f],f--,c-=8);for(e=s&(1<<-c)-1,s>>=-c,c+=n;c>0;e=256*e+t[f],f--,c-=8);if(0===s)s=1-u;else{if(s===o)return e?NaN:a?-P:P;e+=M(2,n),s-=u}return(a?-1:1)*e*M(2,s-n)},G=function(t){return t[3]<<24|t[2]<<16|t[1]<<8|t[0]},B=function(t){return[255&t]},V=function(t){return[255&t,t>>8&255]},z=function(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]},q=function(t){return U(t,52,8)},K=function(t){return U(t,23,4)},J=function(t,n,r){p(t[m],n,{get:function(){return this[r]}})},Y=function(t,n,r,e){var i=+r,o=l(i);if(i!=o||o<0||o+n>t[C])throw E(w);var u=t[R]._b,c=o+t[D],f=u.slice(c,c+n);return e?f:f.reverse()},H=function(t,n,r,e,i,o){var u=+r,c=l(u);if(u!=c||c<0||c+n>t[C])throw E(w);for(var f=t[R]._b,a=c+t[D],s=e(+i),h=0;h<n;h++)f[a+h]=s[o?h:n-h-1]},$=function(t,n){s(t,S,g);var r=+n,e=h(r);if(r!=e)throw E(x);return e};if(u.ABV){if(!a(function(){new S})||!a(function(){new S(.5)})){S=function(t){return new j($(this,t))};for(var X,Q=S[m]=j[m],Z=v(j),tt=0;Z.length>tt;)(X=Z[tt++])in S||c(S,X,j[X]);o||(Q.constructor=S)}var nt=new _(new S(2)),rt=_[m].setInt8;nt.setInt8(0,2147483648),nt.setInt8(1,2147483649),!nt.getInt8(0)&&nt.getInt8(1)||f(_[m],{setInt8:function(t,n){rt.call(this,t,n<<24>>24)},setUint8:function(t,n){rt.call(this,t,n<<24>>24)}},!0)}else S=function(t){var n=$(this,t);this._b=d.call(Array(n),0),this[C]=n},_=function(t,n,r){s(this,_,b),s(t,S,b);var e=t[C],i=l(n);if(i<0||i>e)throw E("Wrong offset!");if(r=void 0===r?e-i:h(r),i+r>e)throw E(x);this[R]=t,this[D]=i,this[C]=r},i&&(J(S,k,"_l"),J(_,I,"_b"),J(_,k,"_l"),J(_,L,"_o")),f(_[m],{getInt8:function(t){return Y(this,1,t)[0]<<24>>24},getUint8:function(t){return Y(this,1,t)[0]},getInt16:function(t){var n=Y(this,2,t,arguments[1]);return(n[1]<<8|n[0])<<16>>16},getUint16:function(t){var n=Y(this,2,t,arguments[1]);return n[1]<<8|n[0]},getInt32:function(t){return G(Y(this,4,t,arguments[1]))},getUint32:function(t){return G(Y(this,4,t,arguments[1]))>>>0},getFloat32:function(t){return W(Y(this,4,t,arguments[1]),23,4)},getFloat64:function(t){return W(Y(this,8,t,arguments[1]),52,8)},setInt8:function(t,n){H(this,1,t,B,n)},setUint8:function(t,n){H(this,1,t,B,n)},setInt16:function(t,n){H(this,2,t,V,n,arguments[2])},setUint16:function(t,n){H(this,2,t,V,n,arguments[2])},setInt32:function(t,n){H(this,4,t,z,n,arguments[2])},setUint32:function(t,n){H(this,4,t,z,n,arguments[2])},setFloat32:function(t,n){H(this,4,t,K,n,arguments[2])},setFloat64:function(t,n){H(this,8,t,q,n,arguments[2])}});y(S,g),y(_,b),c(_[m],u.VIEW,!0),n[g]=S,n[b]=_},function(t,n,r){var e=r(3),i=r(52),o=r(69),u=r(182),c=r(11).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){var e=r(114),i=r(7)("iterator"),o=r(80);t.exports=r(52).getIteratorMethod=function(t){if(void 0!=t)return t[i]||t["@@iterator"]||o[e(t)]}},function(t,n,r){"use strict";var e=r(78),i=r(170),o=r(80),u=r(30);t.exports=r(140)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):"keys"==n?i(0,r):"values"==n?i(0,t[r]):i(0,[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n){function r(t,n){t.classList?t.classList.add(n):t.className+=" "+n}t.exports=r},function(t,n){function r(t,n){if(t.classList)t.classList.remove(n);else{var r=new RegExp("(^|\\b)"+n.split(" ").join("|")+"(\\b|$)","gi");t.className=t.className.replace(r," ")}}t.exports=r},function(t,n){function r(){throw new Error("setTimeout has not been defined")}function e(){throw new Error("clearTimeout has not been defined")}function i(t){if(s===setTimeout)return setTimeout(t,0);if((s===r||!s)&&setTimeout)return s=setTimeout,setTimeout(t,0);try{return s(t,0)}catch(n){try{return s.call(null,t,0)}catch(n){return s.call(this,t,0)}}}function o(t){if(l===clearTimeout)return clearTimeout(t);if((l===e||!l)&&clearTimeout)return l=clearTimeout,clearTimeout(t);try{return l(t)}catch(n){try{return l.call(null,t)}catch(n){return l.call(this,t)}}}function u(){d&&v&&(d=!1,v.length?p=v.concat(p):y=-1,p.length&&c())}function c(){if(!d){var t=i(u);d=!0;for(var n=p.length;n;){for(v=p,p=[];++y<n;)v&&v[y].run();y=-1,n=p.length}v=null,d=!1,o(t)}}function f(t,n){this.fun=t,this.array=n}function a(){}var s,l,h=t.exports={};!function(){try{s="function"==typeof setTimeout?setTimeout:r}catch(t){s=r}try{l="function"==typeof clearTimeout?clearTimeout:e}catch(t){l=e}}();var v,p=[],d=!1,y=-1;h.nextTick=function(t){var n=new Array(arguments.length-1);if(arguments.length>1)for(var r=1;r<arguments.length;r++)n[r-1]=arguments[r];p.push(new f(t,n)),1!==p.length||d||i(c)},f.prototype.run=function(){this.fun.apply(null,this.array)},h.title="browser",h.browser=!0,h.env={},h.argv=[],h.version="",h.versions={},h.on=a,h.addListener=a,h.once=a,h.off=a,h.removeListener=a,h.removeAllListeners=a,h.emit=a,h.prependListener=a,h.prependOnceListener=a,h.listeners=function(t){return[]},h.binding=function(t){throw new Error("process.binding is not supported")},h.cwd=function(){return"/"},h.chdir=function(t){throw new Error("process.chdir is not supported")},h.umask=function(){return 0}},function(t,n,r){var e=r(45);t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},function(t,n,r){"use strict";var e=r(17),i=r(75),o=r(16);t.exports=[].copyWithin||function(t,n){var r=e(this),u=o(r.length),c=i(t,u),f=i(n,u),a=arguments.length>2?arguments[2]:void 0,s=Math.min((void 0===a?u:i(a,u))-f,u-c),l=1;for(f<c&&c<f+s&&(l=-1,f+=s-1,c+=s-1);s-- >0;)f in r?r[c]=r[f]:delete r[c],c+=l,f+=l;return r}},function(t,n,r){var e=r(79);t.exports=function(t,n){var r=[];return e(t,!1,r.push,r,n),r}},function(t,n,r){var e=r(26),i=r(17),o=r(115),u=r(16);t.exports=function(t,n,r,c,f){e(n);var a=i(t),s=o(a),l=u(a.length),h=f?l-1:0,v=f?-1:1;if(r<2)for(;;){if(h in s){c=s[h],h+=v;break}if(h+=v,f?h<0:l<=h)throw TypeError("Reduce of empty array with no initial value")}for(;f?h>=0:l>h;h+=v)h in s&&(c=n(c,s[h],h,a));return c}},function(t,n,r){"use strict";var e=r(26),i=r(6),o=r(121),u=[].slice,c={},f=function(t,n,r){if(!(n in c)){for(var e=[],i=0;i<n;i++)e[i]="a["+i+"]";c[n]=Function("F,a","return new F("+e.join(",")+")")}return c[n](t,r)};t.exports=Function.bind||function(t){var n=e(this),r=u.call(arguments,1),c=function(){var e=r.concat(u.call(arguments));return this instanceof c?f(n,e.length,e):o(n,e,t)};return i(n.prototype)&&(c.prototype=n.prototype),c}},function(t,n,r){"use strict";var e=r(11).f,i=r(70),o=r(73),u=r(53),c=r(68),f=r(46),a=r(79),s=r(140),l=r(170),h=r(74),v=r(10),p=r(65).fastKey,d=v?"_s":"size",y=function(t,n){var r,e=p(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,n,r,s){var l=t(function(t,e){c(t,l,n,"_i"),t._i=i(null),t._f=void 0,t._l=void 0,t[d]=0,void 0!=e&&a(e,r,t[s],t)});return o(l.prototype,{clear:function(){for(var t=this,n=t._i,r=t._f;r;r=r.n)r.r=!0,r.p&&(r.p=r.p.n=void 0),delete n[r.i];t._f=t._l=void 0,t[d]=0},delete:function(t){var n=this,r=y(n,t);if(r){var e=r.n,i=r.p;delete n._i[r.i],r.r=!0,i&&(i.n=e),e&&(e.p=i),n._f==r&&(n._f=e),n._l==r&&(n._l=i),n[d]--}return!!r},forEach:function(t){c(this,l,"forEach");for(var n,r=u(t,arguments.length>1?arguments[1]:void 0,3);n=n?n.n:this._f;)for(r(n.v,n.k,this);n&&n.r;)n=n.p},has:function(t){return!!y(this,t)}}),v&&e(l.prototype,"size",{get:function(){return f(this[d])}}),l},def:function(t,n,r){var e,i,o=y(t,n);return o?o.v=r:(t._l=o={i:i=p(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=o),e&&(e.n=o),t[d]++,"F"!==i&&(t._i[i]=o)),t},getEntry:y,setStrong:function(t,n,r){s(t,n,function(t,n){this._t=t,this._k=n,this._l=void 0},function(){for(var t=this,n=t._k,r=t._l;r&&r.r;)r=r.p;return t._t&&(t._l=r=r?r.n:t._t._f)?"keys"==n?l(0,r.k):"values"==n?l(0,r.v):l(0,[r.k,r.v]):(t._t=void 0,l(1))},r?"entries":"values",!r,!0),h(n)}}},function(t,n,r){var e=r(114),i=r(161);t.exports=function(t){return function(){if(e(this)!=t)throw TypeError(t+"#toJSON isn't generic");return i(this)}}},function(t,n,r){"use strict";var e=r(73),i=r(65).getWeak,o=r(2),u=r(6),c=r(68),f=r(79),a=r(48),s=r(24),l=a(5),h=a(6),v=0,p=function(t){return t._l||(t._l=new d)},d=function(){this.a=[]},y=function(t,n){return l(t.a,function(t){return t[0]===n})};d.prototype={get:function(t){var n=y(this,t);if(n)return n[1]},has:function(t){return!!y(this,t)},set:function(t,n){var r=y(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(t){var n=h(this.a,function(n){return n[0]===t});return~n&&this.a.splice(n,1),!!~n}},t.exports={getConstructor:function(t,n,r,o){var a=t(function(t,e){c(t,a,n,"_i"),t._i=v++,t._l=void 0,void 0!=e&&f(e,r,t[o],t)});return e(a.prototype,{delete:function(t){if(!u(t))return!1;var n=i(t);return!0===n?p(this).delete(t):n&&s(n,this._i)&&delete n[this._i]},has:function(t){if(!u(t))return!1;var n=i(t);return!0===n?p(this).has(t):n&&s(n,this._i)}}),a},def:function(t,n,r){var e=i(o(n),!0);return!0===e?p(t).set(n,r):e[t._i]=r,t},ufstore:p}},function(t,n,r){t.exports=!r(10)&&!r(4)(function(){return 7!=Object.defineProperty(r(132)("div"),"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(6),i=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&i(t)===t}},function(t,n,r){var e=r(2);t.exports=function(t,n,r,i){try{return i?n(e(r)[0],r[1]):n(r)}catch(n){var o=t.return;throw void 0!==o&&e(o.call(t)),n}}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n){t.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&t<1e-8?t-t*t/2:Math.log(1+t)}},function(t,n,r){"use strict";var e=r(72),i=r(125),o=r(116),u=r(17),c=r(115),f=Object.assign;t.exports=!f||r(4)(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=f({},t)[r]||Object.keys(f({},n)).join("")!=e})?function(t,n){for(var r=u(t),f=arguments.length,a=1,s=i.f,l=o.f;f>a;)for(var h,v=c(arguments[a++]),p=s?e(v).concat(s(v)):e(v),d=p.length,y=0;d>y;)l.call(v,h=p[y++])&&(r[h]=v[h]);return r}:f},function(t,n,r){var e=r(11),i=r(2),o=r(72);t.exports=r(10)?Object.defineProperties:function(t,n){i(t);for(var r,u=o(n),c=u.length,f=0;c>f;)e.f(t,r=u[f++],n[r]);return t}},function(t,n,r){var e=r(30),i=r(71).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],c=function(t){try{return i(t)}catch(t){return u.slice()}};t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?c(t):i(e(t))}},function(t,n,r){var e=r(24),i=r(30),o=r(117)(!1),u=r(145)("IE_PROTO");t.exports=function(t,n){var r,c=i(t),f=0,a=[];for(r in c)r!=u&&e(c,r)&&a.push(r);for(;n.length>f;)e(c,r=n[f++])&&(~o(a,r)||a.push(r));return a}},function(t,n,r){var e=r(72),i=r(30),o=r(116).f;t.exports=function(t){return function(n){for(var r,u=i(n),c=e(u),f=c.length,a=0,s=[];f>a;)o.call(u,r=c[a++])&&s.push(t?[r,u[r]]:u[r]);return s}}},function(t,n,r){var e=r(71),i=r(125),o=r(2),u=r(3).Reflect;t.exports=u&&u.ownKeys||function(t){var n=e.f(o(t)),r=i.f;return r?n.concat(r(t)):n}},function(t,n,r){var e=r(3).parseFloat,i=r(82).trim;t.exports=1/e(r(150)+"-0")!=-1/0?function(t){var n=i(String(t),3),r=e(n);return 0===r&&"-"==n.charAt(0)?-0:r}:e},function(t,n,r){var e=r(3).parseInt,i=r(82).trim,o=r(150),u=/^[\-+]?0[xX]/;t.exports=8!==e(o+"08")||22!==e(o+"0x16")?function(t,n){var r=i(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},function(t,n,r){var e=r(16),i=r(149),o=r(46);t.exports=function(t,n,r,u){var c=String(o(t)),f=c.length,a=void 0===r?" ":String(r),s=e(n);if(s<=f||""==a)return c;var l=s-f,h=i.call(a,Math.ceil(l/a.length));return h.length>l&&(h=h.slice(0,l)),u?h+c:c+h}},function(t,n,r){n.f=r(7)},function(t,n,r){"use strict";var e=r(164);t.exports=r(118)("Map",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(this,t);return n&&n.v},set:function(t,n){return e.def(this,0===t?0:t,n)}},e,!0)},function(t,n,r){r(10)&&"g"!=/./g.flags&&r(11).f(RegExp.prototype,"flags",{configurable:!0,get:r(120)})},function(t,n,r){"use strict";var e=r(164);t.exports=r(118)("Set",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(this,t=0===t?0:t,t)}},e)},function(t,n,r){"use strict";var e,i=r(48)(0),o=r(28),u=r(65),c=r(172),f=r(166),a=r(6),s=u.getWeak,l=Object.isExtensible,h=f.ufstore,v={},p=function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},d={get:function(t){if(a(t)){var n=s(t);return!0===n?h(this).get(t):n?n[this._i]:void 0}},set:function(t,n){return f.def(this,t,n)}},y=t.exports=r(118)("WeakMap",p,d,f,!0,!0);7!=(new y).set((Object.freeze||Object)(v),7).get(v)&&(e=f.getConstructor(p),c(e.prototype,d),u.NEED=!0,i(["delete","has","get","set"],function(t){var n=y.prototype,r=n[t];o(n,t,function(n,i){if(a(n)&&!l(n)){this._f||(this._f=new e);var o=this._f[t](n,i);return"set"==t?this:o}return r.call(this,n,i)})}))},,,,function(t,n){"use strict";function r(){var t=document.querySelector("#page-nav");if(t&&!document.querySelector("#page-nav .extend.prev")&&(t.innerHTML='<a class="extend prev disabled" rel="prev">&laquo; Prev</a>'+t.innerHTML),t&&!document.querySelector("#page-nav .extend.next")&&(t.innerHTML=t.innerHTML+'<a class="extend next disabled" rel="next">Next &raquo;</a>'),yiliaConfig&&yiliaConfig.open_in_new){document.querySelectorAll(".article-entry a:not(.article-more-a)").forEach(function(t){var n=t.getAttribute("target");n&&""!==n||t.setAttribute("target","_blank")})}if(yiliaConfig&&yiliaConfig.toc_hide_index){document.querySelectorAll(".toc-number").forEach(function(t){t.style.display="none"})}var n=document.querySelector("#js-aboutme");n&&0!==n.length&&(n.innerHTML=n.innerText)}t.exports={init:r}},function(t,n,r){"use strict";function e(t){return t&&t.__esModule?t:{default:t}}function i(t,n){var r=/\/|index.html/g;return t.replace(r,"")===n.replace(r,"")}function o(){for(var t=document.querySelectorAll(".js-header-menu li a"),n=window.location.pathname,r=0,e=t.length;r<e;r++){var o=t[r];i(n,o.getAttribute("href"))&&(0,h.default)(o,"active")}}function u(t){for(var n=t.offsetLeft,r=t.offsetParent;null!==r;)n+=r.offsetLeft,r=r.offsetParent;return n}function c(t){for(var n=t.offsetTop,r=t.offsetParent;null!==r;)n+=r.offsetTop,r=r.offsetParent;return n}function f(t,n,r,e,i){var o=u(t),f=c(t)-n;if(f-r<=i){var a=t.$newDom;a||(a=t.cloneNode(!0),(0,d.default)(t,a),t.$newDom=a,a.style.position="fixed",a.style.top=(r||f)+"px",a.style.left=o+"px",a.style.zIndex=e||2,a.style.width="100%",a.style.color="#fff"),a.style.visibility="visible",t.style.visibility="hidden"}else{t.style.visibility="visible";var s=t.$newDom;s&&(s.style.visibility="hidden")}}function a(){var t=document.querySelector(".js-overlay"),n=document.querySelector(".js-header-menu");f(t,document.body.scrollTop,-63,2,0),f(n,document.body.scrollTop,1,3,0)}function s(){document.querySelector("#container").addEventListener("scroll",function(t){a()}),window.addEventListener("scroll",function(t){a()}),a()}var l=r(156),h=e(l),v=r(157),p=(e(v),r(382)),d=e(p),y=r(128),g=e(y),b=r(190),m=e(b),x=r(129);(function(){g.default.versions.mobile&&window.screen.width<800&&(o(),s())})(),(0,x.addLoadEvent)(function(){m.default.init()}),t.exports={}},,,,function(t,n,r){(function(t){"use strict";function n(t,n,r){t[n]||Object[e](t,n,{writable:!0,configurable:!0,value:r})}if(r(381),r(391),r(198),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed");t._babelPolyfill=!0;var e="defineProperty";n(String.prototype,"padLeft","".padStart),n(String.prototype,"padRight","".padEnd),"pop,reverse,shift,keys,values,entries,indexOf,every,some,forEach,map,filter,find,findIndex,includes,join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill".split(",").forEach(function(t){[][t]&&n(Array,t,Function.call.bind([][t]))})}).call(n,function(){return this}())},,,function(t,n,r){r(210),t.exports=r(52).RegExp.escape},,,,function(t,n,r){var e=r(6),i=r(138),o=r(7)("species");t.exports=function(t){var n;return i(t)&&(n=t.constructor,"function"!=typeof n||n!==Array&&!i(n.prototype)||(n=void 0),e(n)&&null===(n=n[o])&&(n=void 0)),void 0===n?Array:n}},function(t,n,r){var e=r(202);t.exports=function(t,n){return new(e(t))(n)}},function(t,n,r){"use strict";var e=r(2),i=r(50),o="number";t.exports=function(t){if("string"!==t&&t!==o&&"default"!==t)throw TypeError("Incorrect hint");return i(e(this),t!=o)}},function(t,n,r){var e=r(72),i=r(125),o=r(116);t.exports=function(t){var n=e(t),r=i.f;if(r)for(var u,c=r(t),f=o.f,a=0;c.length>a;)f.call(t,u=c[a++])&&n.push(u);return n}},function(t,n,r){var e=r(72),i=r(30);t.exports=function(t,n){for(var r,o=i(t),u=e(o),c=u.length,f=0;c>f;)if(o[r=u[f++]]===n)return r}},function(t,n,r){"use strict";var e=r(208),i=r(121),o=r(26);t.exports=function(){for(var t=o(this),n=arguments.length,r=Array(n),u=0,c=e._,f=!1;n>u;)(r[u]=arguments[u++])===c&&(f=!0);return function(){var e,o=this,u=arguments.length,a=0,s=0;if(!f&&!u)return i(t,r,o);if(e=r.slice(),f)for(;n>a;a++)e[a]===c&&(e[a]=arguments[s++]);for(;u>s;)e.push(arguments[s++]);return i(t,e,o)}}},function(t,n,r){t.exports=r(3)},function(t,n){t.exports=function(t,n){var r=n===Object(n)?function(t){return n[t]}:n;return function(n){return String(n).replace(t,r)}}},function(t,n,r){var e=r(1),i=r(209)(/[\\^$*+?.()|[\]{}]/g,"\\$&");e(e.S,"RegExp",{escape:function(t){return i(t)}})},function(t,n,r){var e=r(1);e(e.P,"Array",{copyWithin:r(160)}),r(78)("copyWithin")},function(t,n,r){"use strict";var e=r(1),i=r(48)(4);e(e.P+e.F*!r(47)([].every,!0),"Array",{every:function(t){return i(this,t,arguments[1])}})},function(t,n,r){var e=r(1);e(e.P,"Array",{fill:r(130)}),r(78)("fill")},function(t,n,r){"use strict";var e=r(1),i=r(48)(2);e(e.P+e.F*!r(47)([].filter,!0),"Array",{filter:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(48)(6),o="findIndex",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{findIndex:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(78)(o)},function(t,n,r){"use strict";var e=r(1),i=r(48)(5),o="find",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{find:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(78)(o)},function(t,n,r){"use strict";var e=r(1),i=r(48)(0),o=r(47)([].forEach,!0);e(e.P+e.F*!o,"Array",{forEach:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(53),i=r(1),o=r(17),u=r(169),c=r(137),f=r(16),a=r(131),s=r(154);i(i.S+i.F*!r(123)(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,i,l,h=o(t),v="function"==typeof this?this:Array,p=arguments.length,d=p>1?arguments[1]:void 0,y=void 0!==d,g=0,b=s(h);if(y&&(d=e(d,p>2?arguments[2]:void 0,2)),void 0==b||v==Array&&c(b))for(n=f(h.length),r=new v(n);n>g;g++)a(r,g,y?d(h[g],g):h[g]);else for(l=b.call(h),r=new v;!(i=l.next()).done;g++)a(r,g,y?u(l,d,[i.value,g],!0):i.value);return r.length=g,r}})},function(t,n,r){"use strict";var e=r(1),i=r(117)(!1),o=[].indexOf,u=!!o&&1/[1].indexOf(1,-0)<0;e(e.P+e.F*(u||!r(47)(o)),"Array",{indexOf:function(t){return u?o.apply(this,arguments)||0:i(this,t,arguments[1])}})},function(t,n,r){var e=r(1);e(e.S,"Array",{isArray:r(138)})},function(t,n,r){"use strict";var e=r(1),i=r(30),o=[].join;e(e.P+e.F*(r(115)!=Object||!r(47)(o)),"Array",{join:function(t){return o.call(i(this),void 0===t?",":t)}})},function(t,n,r){"use strict";var e=r(1),i=r(30),o=r(67),u=r(16),c=[].lastIndexOf,f=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(f||!r(47)(c)),"Array",{lastIndexOf:function(t){if(f)return c.apply(this,arguments)||0;var n=i(this),r=u(n.length),e=r-1;for(arguments.length>1&&(e=Math.min(e,o(arguments[1]))),e<0&&(e=r+e);e>=0;e--)if(e in n&&n[e]===t)return e||0;return-1}})},function(t,n,r){"use strict";var e=r(1),i=r(48)(1);e(e.P+e.F*!r(47)([].map,!0),"Array",{map:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(131);e(e.S+e.F*r(4)(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);n>t;)i(r,t,arguments[t++]);return r.length=n,r}})},function(t,n,r){"use strict";var e=r(1),i=r(162);e(e.P+e.F*!r(47)([].reduceRight,!0),"Array",{reduceRight:function(t){return i(this,t,arguments.length,arguments[1],!0)}})},function(t,n,r){"use strict";var e=r(1),i=r(162);e(e.P+e.F*!r(47)([].reduce,!0),"Array",{reduce:function(t){return i(this,t,arguments.length,arguments[1],!1)}})},function(t,n,r){"use strict";var e=r(1),i=r(135),o=r(45),u=r(75),c=r(16),f=[].slice;e(e.P+e.F*r(4)(function(){i&&f.call(i)}),"Array",{slice:function(t,n){var r=c(this.length),e=o(this);if(n=void 0===n?r:n,"Array"==e)return f.call(this,t,n);for(var i=u(t,r),a=u(n,r),s=c(a-i),l=Array(s),h=0;h<s;h++)l[h]="String"==e?this.charAt(i+h):this[i+h];return l}})},function(t,n,r){"use strict";var e=r(1),i=r(48)(3);e(e.P+e.F*!r(47)([].some,!0),"Array",{some:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(26),o=r(17),u=r(4),c=[].sort,f=[1,2,3];e(e.P+e.F*(u(function(){f.sort(void 0)})||!u(function(){f.sort(null)})||!r(47)(c)),"Array",{sort:function(t){return void 0===t?c.call(o(this)):c.call(o(this),i(t))}})},function(t,n,r){r(74)("Array")},function(t,n,r){var e=r(1);e(e.S,"Date",{now:function(){return(new Date).getTime()}})},function(t,n,r){"use strict";var e=r(1),i=r(4),o=Date.prototype.getTime,u=function(t){return t>9?t:"0"+t};e(e.P+e.F*(i(function(){return"0385-07-25T07:06:39.999Z"!=new Date(-5e13-1).toISOString()})||!i(function(){new Date(NaN).toISOString()})),"Date",{toISOString:function(){
if(!isFinite(o.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":n>9999?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(r>99?r:"0"+u(r))+"Z"}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(50);e(e.P+e.F*r(4)(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=i(this),r=o(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},function(t,n,r){var e=r(7)("toPrimitive"),i=Date.prototype;e in i||r(27)(i,e,r(204))},function(t,n,r){var e=Date.prototype,i="Invalid Date",o="toString",u=e[o],c=e.getTime;new Date(NaN)+""!=i&&r(28)(e,o,function(){var t=c.call(this);return t===t?u.call(this):i})},function(t,n,r){var e=r(1);e(e.P,"Function",{bind:r(163)})},function(t,n,r){"use strict";var e=r(6),i=r(32),o=r(7)("hasInstance"),u=Function.prototype;o in u||r(11).f(u,o,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=i(t);)if(this.prototype===t)return!0;return!1}})},function(t,n,r){var e=r(11).f,i=r(66),o=r(24),u=Function.prototype,c="name",f=Object.isExtensible||function(){return!0};c in u||r(10)&&e(u,c,{configurable:!0,get:function(){try{var t=this,n=(""+t).match(/^\s*function ([^ (]*)/)[1];return o(t,c)||!f(t)||e(t,c,i(5,n)),n}catch(t){return""}}})},function(t,n,r){var e=r(1),i=r(171),o=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:t>94906265.62425156?Math.log(t)+Math.LN2:i(t-1+o(t-1)*o(t+1))}})},function(t,n,r){function e(t){return isFinite(t=+t)&&0!=t?t<0?-e(-t):Math.log(t+Math.sqrt(t*t+1)):t}var i=r(1),o=Math.asinh;i(i.S+i.F*!(o&&1/o(0)>0),"Math",{asinh:e})},function(t,n,r){var e=r(1),i=Math.atanh;e(e.S+e.F*!(i&&1/i(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},function(t,n,r){var e=r(1),i=r(142);e(e.S,"Math",{cbrt:function(t){return i(t=+t)*Math.pow(Math.abs(t),1/3)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},function(t,n,r){var e=r(1),i=Math.exp;e(e.S,"Math",{cosh:function(t){return(i(t=+t)+i(-t))/2}})},function(t,n,r){var e=r(1),i=r(141);e(e.S+e.F*(i!=Math.expm1),"Math",{expm1:i})},function(t,n,r){var e=r(1),i=r(142),o=Math.pow,u=o(2,-52),c=o(2,-23),f=o(2,127)*(2-c),a=o(2,-126),s=function(t){return t+1/u-1/u};e(e.S,"Math",{fround:function(t){var n,r,e=Math.abs(t),o=i(t);return e<a?o*s(e/a/c)*a*c:(n=(1+c/u)*e,r=n-(n-e),r>f||r!=r?o*(1/0):o*r)}})},function(t,n,r){var e=r(1),i=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,o=0,u=0,c=arguments.length,f=0;u<c;)r=i(arguments[u++]),f<r?(e=f/r,o=o*e*e+1,f=r):r>0?(e=r/f,o+=e*e):o+=r;return f===1/0?1/0:f*Math.sqrt(o)}})},function(t,n,r){var e=r(1),i=Math.imul;e(e.S+e.F*r(4)(function(){return-5!=i(4294967295,5)||2!=i.length}),"Math",{imul:function(t,n){var r=65535,e=+t,i=+n,o=r&e,u=r&i;return 0|o*u+((r&e>>>16)*u+o*(r&i>>>16)<<16>>>0)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{log10:function(t){return Math.log(t)/Math.LN10}})},function(t,n,r){var e=r(1);e(e.S,"Math",{log1p:r(171)})},function(t,n,r){var e=r(1);e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},function(t,n,r){var e=r(1);e(e.S,"Math",{sign:r(142)})},function(t,n,r){var e=r(1),i=r(141),o=Math.exp;e(e.S+e.F*r(4)(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(i(t)-i(-t))/2:(o(t-1)-o(-t-1))*(Math.E/2)}})},function(t,n,r){var e=r(1),i=r(141),o=Math.exp;e(e.S,"Math",{tanh:function(t){var n=i(t=+t),r=i(-t);return n==1/0?1:r==1/0?-1:(n-r)/(o(t)+o(-t))}})},function(t,n,r){var e=r(1);e(e.S,"Math",{trunc:function(t){return(t>0?Math.floor:Math.ceil)(t)}})},function(t,n,r){"use strict";var e=r(3),i=r(24),o=r(45),u=r(136),c=r(50),f=r(4),a=r(71).f,s=r(31).f,l=r(11).f,h=r(82).trim,v="Number",p=e[v],d=p,y=p.prototype,g=o(r(70)(y))==v,b="trim"in String.prototype,m=function(t){var n=c(t,!1);if("string"==typeof n&&n.length>2){n=b?n.trim():h(n,3);var r,e,i,o=n.charCodeAt(0);if(43===o||45===o){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===o){switch(n.charCodeAt(1)){case 66:case 98:e=2,i=49;break;case 79:case 111:e=8,i=55;break;default:return+n}for(var u,f=n.slice(2),a=0,s=f.length;a<s;a++)if((u=f.charCodeAt(a))<48||u>i)return NaN;return parseInt(f,e)}}return+n};if(!p(" 0o1")||!p("0b1")||p("+0x1")){p=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof p&&(g?f(function(){y.valueOf.call(r)}):o(r)!=v)?u(new d(m(n)),r,p):m(n)};for(var x,w=r(10)?a(d):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),S=0;w.length>S;S++)i(d,x=w[S])&&!i(p,x)&&l(p,x,s(d,x));p.prototype=y,y.constructor=p,r(28)(e,v,p)}},function(t,n,r){var e=r(1);e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},function(t,n,r){var e=r(1),i=r(3).isFinite;e(e.S,"Number",{isFinite:function(t){return"number"==typeof t&&i(t)}})},function(t,n,r){var e=r(1);e(e.S,"Number",{isInteger:r(168)})},function(t,n,r){var e=r(1);e(e.S,"Number",{isNaN:function(t){return t!=t}})},function(t,n,r){var e=r(1),i=r(168),o=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return i(t)&&o(t)<=9007199254740991}})},function(t,n,r){var e=r(1);e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},function(t,n,r){var e=r(1);e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},function(t,n,r){var e=r(1),i=r(178);e(e.S+e.F*(Number.parseFloat!=i),"Number",{parseFloat:i})},function(t,n,r){var e=r(1),i=r(179);e(e.S+e.F*(Number.parseInt!=i),"Number",{parseInt:i})},function(t,n,r){"use strict";var e=r(1),i=r(67),o=r(159),u=r(149),c=1..toFixed,f=Math.floor,a=[0,0,0,0,0,0],s="Number.toFixed: incorrect invocation!",l="0",h=function(t,n){for(var r=-1,e=n;++r<6;)e+=t*a[r],a[r]=e%1e7,e=f(e/1e7)},v=function(t){for(var n=6,r=0;--n>=0;)r+=a[n],a[n]=f(r/t),r=r%t*1e7},p=function(){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==a[t]){var r=String(a[t]);n=""===n?r:n+u.call(l,7-r.length)+r}return n},d=function(t,n,r){return 0===n?r:n%2==1?d(t,n-1,r*t):d(t*t,n/2,r)},y=function(t){for(var n=0,r=t;r>=4096;)n+=12,r/=4096;for(;r>=2;)n+=1,r/=2;return n};e(e.P+e.F*(!!c&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r(4)(function(){c.call({})})),"Number",{toFixed:function(t){var n,r,e,c,f=o(this,s),a=i(t),g="",b=l;if(a<0||a>20)throw RangeError(s);if(f!=f)return"NaN";if(f<=-1e21||f>=1e21)return String(f);if(f<0&&(g="-",f=-f),f>1e-21)if(n=y(f*d(2,69,1))-69,r=n<0?f*d(2,-n,1):f/d(2,n,1),r*=4503599627370496,(n=52-n)>0){for(h(0,r),e=a;e>=7;)h(1e7,0),e-=7;for(h(d(10,e,1),0),e=n-1;e>=23;)v(1<<23),e-=23;v(1<<e),h(1,1),v(2),b=p()}else h(0,r),h(1<<-n,0),b=p()+u.call(l,a);return a>0?(c=b.length,b=g+(c<=a?"0."+u.call(l,a-c)+b:b.slice(0,c-a)+"."+b.slice(c-a))):b=g+b,b}})},function(t,n,r){"use strict";var e=r(1),i=r(4),o=r(159),u=1..toPrecision;e(e.P+e.F*(i(function(){return"1"!==u.call(1,void 0)})||!i(function(){u.call({})})),"Number",{toPrecision:function(t){var n=o(this,"Number#toPrecision: incorrect invocation!");return void 0===t?u.call(n):u.call(n,t)}})},function(t,n,r){var e=r(1);e(e.S+e.F,"Object",{assign:r(172)})},function(t,n,r){var e=r(1);e(e.S,"Object",{create:r(70)})},function(t,n,r){var e=r(1);e(e.S+e.F*!r(10),"Object",{defineProperties:r(173)})},function(t,n,r){var e=r(1);e(e.S+e.F*!r(10),"Object",{defineProperty:r(11).f})},function(t,n,r){var e=r(6),i=r(65).onFreeze;r(49)("freeze",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(30),i=r(31).f;r(49)("getOwnPropertyDescriptor",function(){return function(t,n){return i(e(t),n)}})},function(t,n,r){r(49)("getOwnPropertyNames",function(){return r(174).f})},function(t,n,r){var e=r(17),i=r(32);r(49)("getPrototypeOf",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(6);r(49)("isExtensible",function(t){return function(n){return!!e(n)&&(!t||t(n))}})},function(t,n,r){var e=r(6);r(49)("isFrozen",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(6);r(49)("isSealed",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(1);e(e.S,"Object",{is:r(180)})},function(t,n,r){var e=r(17),i=r(72);r(49)("keys",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(6),i=r(65).onFreeze;r(49)("preventExtensions",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(6),i=r(65).onFreeze;r(49)("seal",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(1);e(e.S,"Object",{setPrototypeOf:r(144).set})},function(t,n,r){"use strict";var e=r(114),i={};i[r(7)("toStringTag")]="z",i+""!="[object z]"&&r(28)(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},function(t,n,r){var e=r(1),i=r(178);e(e.G+e.F*(parseFloat!=i),{parseFloat:i})},function(t,n,r){var e=r(1),i=r(179);e(e.G+e.F*(parseInt!=i),{parseInt:i})},function(t,n,r){"use strict";var e,i,o,u=r(69),c=r(3),f=r(53),a=r(114),s=r(1),l=r(6),h=r(26),v=r(68),p=r(79),d=r(146),y=r(151).set,g=r(143)(),b="Promise",m=c.TypeError,x=c.process,w=c[b],x=c.process,S="process"==a(x),_=function(){},O=!!function(){try{var t=w.resolve(1),n=(t.constructor={})[r(7)("species")]=function(t){t(_,_)};return(S||"function"==typeof PromiseRejectionEvent)&&t.then(_)instanceof n}catch(t){}}(),E=function(t,n){return t===n||t===w&&n===o},P=function(t){var n;return!(!l(t)||"function"!=typeof(n=t.then))&&n},j=function(t){return E(w,t)?new F(t):new i(t)},F=i=function(t){var n,r;this.promise=new t(function(t,e){if(void 0!==n||void 0!==r)throw m("Bad Promise constructor");n=t,r=e}),this.resolve=h(n),this.reject=h(r)},M=function(t){try{t()}catch(t){return{error:t}}},A=function(t,n){if(!t._n){t._n=!0;var r=t._c;g(function(){for(var e=t._v,i=1==t._s,o=0;r.length>o;)!function(n){var r,o,u=i?n.ok:n.fail,c=n.resolve,f=n.reject,a=n.domain;try{u?(i||(2==t._h&&I(t),t._h=1),!0===u?r=e:(a&&a.enter(),r=u(e),a&&a.exit()),r===n.promise?f(m("Promise-chain cycle")):(o=P(r))?o.call(r,c,f):c(r)):f(e)}catch(t){f(t)}}(r[o++]);t._c=[],t._n=!1,n&&!t._h&&N(t)})}},N=function(t){y.call(c,function(){var n,r,e,i=t._v;if(T(t)&&(n=M(function(){S?x.emit("unhandledRejection",i,t):(r=c.onunhandledrejection)?r({promise:t,reason:i}):(e=c.console)&&e.error&&e.error("Unhandled promise rejection",i)}),t._h=S||T(t)?2:1),t._a=void 0,n)throw n.error})},T=function(t){if(1==t._h)return!1;for(var n,r=t._a||t._c,e=0;r.length>e;)if(n=r[e++],n.fail||!T(n.promise))return!1;return!0},I=function(t){y.call(c,function(){var n;S?x.emit("rejectionHandled",t):(n=c.onrejectionhandled)&&n({promise:t,reason:t._v})})},k=function(t){var n=this;n._d||(n._d=!0,n=n._w||n,n._v=t,n._s=2,n._a||(n._a=n._c.slice()),A(n,!0))},L=function(t){var n,r=this;if(!r._d){r._d=!0,r=r._w||r;try{if(r===t)throw m("Promise can't be resolved itself");(n=P(t))?g(function(){var e={_w:r,_d:!1};try{n.call(t,f(L,e,1),f(k,e,1))}catch(t){k.call(e,t)}}):(r._v=t,r._s=1,A(r,!1))}catch(t){k.call({_w:r,_d:!1},t)}}};O||(w=function(t){v(this,w,b,"_h"),h(t),e.call(this);try{t(f(L,this,1),f(k,this,1))}catch(t){k.call(this,t)}},e=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1},e.prototype=r(73)(w.prototype,{then:function(t,n){var r=j(d(this,w));return r.ok="function"!=typeof t||t,r.fail="function"==typeof n&&n,r.domain=S?x.domain:void 0,this._c.push(r),this._a&&this._a.push(r),this._s&&A(this,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),F=function(){var t=new e;this.promise=t,this.resolve=f(L,t,1),this.reject=f(k,t,1)}),s(s.G+s.W+s.F*!O,{Promise:w}),r(81)(w,b),r(74)(b),o=r(52)[b],s(s.S+s.F*!O,b,{reject:function(t){var n=j(this);return(0,n.reject)(t),n.promise}}),s(s.S+s.F*(u||!O),b,{resolve:function(t){if(t instanceof w&&E(t.constructor,this))return t;var n=j(this);return(0,n.resolve)(t),n.promise}}),s(s.S+s.F*!(O&&r(123)(function(t){w.all(t).catch(_)})),b,{all:function(t){var n=this,r=j(n),e=r.resolve,i=r.reject,o=M(function(){var r=[],o=0,u=1;p(t,!1,function(t){var c=o++,f=!1;r.push(void 0),u++,n.resolve(t).then(function(t){f||(f=!0,r[c]=t,--u||e(r))},i)}),--u||e(r)});return o&&i(o.error),r.promise},race:function(t){var n=this,r=j(n),e=r.reject,i=M(function(){p(t,!1,function(t){n.resolve(t).then(r.resolve,e)})});return i&&e(i.error),r.promise}})},function(t,n,r){var e=r(1),i=r(26),o=r(2),u=(r(3).Reflect||{}).apply,c=Function.apply;e(e.S+e.F*!r(4)(function(){u(function(){})}),"Reflect",{apply:function(t,n,r){var e=i(t),f=o(r);return u?u(e,n,f):c.call(e,n,f)}})},function(t,n,r){var e=r(1),i=r(70),o=r(26),u=r(2),c=r(6),f=r(4),a=r(163),s=(r(3).Reflect||{}).construct,l=f(function(){function t(){}return!(s(function(){},[],t)instanceof t)}),h=!f(function(){s(function(){})});e(e.S+e.F*(l||h),"Reflect",{construct:function(t,n){o(t),u(n);var r=arguments.length<3?t:o(arguments[2]);if(h&&!l)return s(t,n,r);if(t==r){switch(n.length){case 0:return new t;case 1:return new t(n[0]);case 2:return new t(n[0],n[1]);case 3:return new t(n[0],n[1],n[2]);case 4:return new t(n[0],n[1],n[2],n[3])}var e=[null];return e.push.apply(e,n),new(a.apply(t,e))}var f=r.prototype,v=i(c(f)?f:Object.prototype),p=Function.apply.call(t,v,n);return c(p)?p:v}})},function(t,n,r){var e=r(11),i=r(1),o=r(2),u=r(50);i(i.S+i.F*r(4)(function(){Reflect.defineProperty(e.f({},1,{value:1}),1,{value:2})}),"Reflect",{defineProperty:function(t,n,r){o(t),n=u(n,!0),o(r);try{return e.f(t,n,r),!0}catch(t){return!1}}})},function(t,n,r){var e=r(1),i=r(31).f,o=r(2);e(e.S,"Reflect",{deleteProperty:function(t,n){var r=i(o(t),n);return!(r&&!r.configurable)&&delete t[n]}})},function(t,n,r){"use strict";var e=r(1),i=r(2),o=function(t){this._t=i(t),this._i=0;var n,r=this._k=[];for(n in t)r.push(n)};r(139)(o,"Object",function(){var t,n=this,r=n._k;do{if(n._i>=r.length)return{value:void 0,done:!0}}while(!((t=r[n._i++])in n._t));return{value:t,done:!1}}),e(e.S,"Reflect",{enumerate:function(t){return new o(t)}})},function(t,n,r){var e=r(31),i=r(1),o=r(2);i(i.S,"Reflect",{getOwnPropertyDescriptor:function(t,n){return e.f(o(t),n)}})},function(t,n,r){var e=r(1),i=r(32),o=r(2);e(e.S,"Reflect",{getPrototypeOf:function(t){return i(o(t))}})},function(t,n,r){function e(t,n){var r,c,s=arguments.length<3?t:arguments[2];return a(t)===s?t[n]:(r=i.f(t,n))?u(r,"value")?r.value:void 0!==r.get?r.get.call(s):void 0:f(c=o(t))?e(c,n,s):void 0}var i=r(31),o=r(32),u=r(24),c=r(1),f=r(6),a=r(2);c(c.S,"Reflect",{get:e})},function(t,n,r){var e=r(1);e(e.S,"Reflect",{has:function(t,n){return n in t}})},function(t,n,r){var e=r(1),i=r(2),o=Object.isExtensible;e(e.S,"Reflect",{isExtensible:function(t){return i(t),!o||o(t)}})},function(t,n,r){var e=r(1);e(e.S,"Reflect",{ownKeys:r(177)})},function(t,n,r){var e=r(1),i=r(2),o=Object.preventExtensions;e(e.S,"Reflect",{preventExtensions:function(t){i(t);try{return o&&o(t),!0}catch(t){return!1}}})},function(t,n,r){var e=r(1),i=r(144);i&&e(e.S,"Reflect",{setPrototypeOf:function(t,n){i.check(t,n);try{return i.set(t,n),!0}catch(t){return!1}}})},function(t,n,r){function e(t,n,r){var f,h,v=arguments.length<4?t:arguments[3],p=o.f(s(t),n);if(!p){if(l(h=u(t)))return e(h,n,r,v);p=a(0)}return c(p,"value")?!(!1===p.writable||!l(v)||(f=o.f(v,n)||a(0),f.value=r,i.f(v,n,f),0)):void 0!==p.set&&(p.set.call(v,r),!0)}var i=r(11),o=r(31),u=r(32),c=r(24),f=r(1),a=r(66),s=r(2),l=r(6);f(f.S,"Reflect",{set:e})},function(t,n,r){var e=r(3),i=r(136),o=r(11).f,u=r(71).f,c=r(122),f=r(120),a=e.RegExp,s=a,l=a.prototype,h=/a/g,v=/a/g,p=new a(h)!==h;if(r(10)&&(!p||r(4)(function(){return v[r(7)("match")]=!1,a(h)!=h||a(v)==v||"/a/i"!=a(h,"i")}))){a=function(t,n){var r=this instanceof a,e=c(t),o=void 0===n;return!r&&e&&t.constructor===a&&o?t:i(p?new s(e&&!o?t.source:t,n):s((e=t instanceof a)?t.source:t,e&&o?f.call(t):n),r?this:l,a)};for(var d=u(s),y=0;d.length>y;)!function(t){t in a||o(a,t,{configurable:!0,get:function(){return s[t]},set:function(n){s[t]=n}})}(d[y++]);l.constructor=a,a.prototype=l,r(28)(e,"RegExp",a)}r(74)("RegExp")},function(t,n,r){r(119)("match",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=void 0==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(119)("replace",2,function(t,n,r){return[function(e,i){"use strict";var o=t(this),u=void 0==e?void 0:e[n];return void 0!==u?u.call(e,o,i):r.call(String(o),e,i)},r]})},function(t,n,r){r(119)("search",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=void 0==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(119)("split",2,function(t,n,e){"use strict";var i=r(122),o=e,u=[].push,c="split",f="length",a="lastIndex";if("c"=="abbc"[c](/(b)*/)[1]||4!="test"[c](/(?:)/,-1)[f]||2!="ab"[c](/(?:ab)*/)[f]||4!="."[c](/(.?)(.?)/)[f]||"."[c](/()()/)[f]>1||""[c](/.?/)[f]){var s=void 0===/()??/.exec("")[1];e=function(t,n){var r=String(this);if(void 0===t&&0===n)return[];if(!i(t))return o.call(r,t,n);var e,c,l,h,v,p=[],d=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),y=0,g=void 0===n?4294967295:n>>>0,b=new RegExp(t.source,d+"g");for(s||(e=new RegExp("^"+b.source+"$(?!\\s)",d));(c=b.exec(r))&&!((l=c.index+c[0][f])>y&&(p.push(r.slice(y,c.index)),!s&&c[f]>1&&c[0].replace(e,function(){for(v=1;v<arguments[f]-2;v++)void 0===arguments[v]&&(c[v]=void 0)}),c[f]>1&&c.index<r[f]&&u.apply(p,c.slice(1)),h=c[0][f],y=l,p[f]>=g));)b[a]===c.index&&b[a]++;return y===r[f]?!h&&b.test("")||p.push(""):p.push(r.slice(y)),p[f]>g?p.slice(0,g):p}}else"0"[c](void 0,0)[f]&&(e=function(t,n){return void 0===t&&0===n?[]:o.call(this,t,n)});return[function(r,i){var o=t(this),u=void 0==r?void 0:r[n];return void 0!==u?u.call(r,o,i):e.call(String(o),r,i)},e]})},function(t,n,r){"use strict";r(184);var e=r(2),i=r(120),o=r(10),u="toString",c=/./[u],f=function(t){r(28)(RegExp.prototype,u,t,!0)};r(4)(function(){return"/a/b"!=c.call({source:"a",flags:"b"})})?f(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!o&&t instanceof RegExp?i.call(t):void 0)}):c.name!=u&&f(function(){return c.call(this)})},function(t,n,r){"use strict";r(29)("anchor",function(t){return function(n){return t(this,"a","name",n)}})},function(t,n,r){"use strict";r(29)("big",function(t){return function(){return t(this,"big","","")}})},function(t,n,r){"use strict";r(29)("blink",function(t){return function(){return t(this,"blink","","")}})},function(t,n,r){"use strict";r(29)("bold",function(t){return function(){return t(this,"b","","")}})},function(t,n,r){"use strict";var e=r(1),i=r(147)(!1);e(e.P,"String",{codePointAt:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(1),i=r(16),o=r(148),u="endsWith",c=""[u];e(e.P+e.F*r(134)(u),"String",{endsWith:function(t){var n=o(this,t,u),r=arguments.length>1?arguments[1]:void 0,e=i(n.length),f=void 0===r?e:Math.min(i(r),e),a=String(t);return c?c.call(n,a,f):n.slice(f-a.length,f)===a}})},function(t,n,r){"use strict";r(29)("fixed",function(t){return function(){return t(this,"tt","","")}})},function(t,n,r){"use strict";r(29)("fontcolor",function(t){return function(n){return t(this,"font","color",n)}})},function(t,n,r){"use strict";r(29)("fontsize",function(t){return function(n){return t(this,"font","size",n)}})},function(t,n,r){var e=r(1),i=r(75),o=String.fromCharCode,u=String.fromCodePoint;e(e.S+e.F*(!!u&&1!=u.length),"String",{fromCodePoint:function(t){for(var n,r=[],e=arguments.length,u=0;e>u;){if(n=+arguments[u++],i(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?o(n):o(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},function(t,n,r){"use strict";var e=r(1),i=r(148),o="includes";e(e.P+e.F*r(134)(o),"String",{includes:function(t){return!!~i(this,t,o).indexOf(t,arguments.length>1?arguments[1]:void 0)}})},function(t,n,r){"use strict";r(29)("italics",function(t){return function(){return t(this,"i","","")}})},function(t,n,r){"use strict";var e=r(147)(!0);r(140)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";r(29)("link",function(t){return function(n){return t(this,"a","href",n)}})},function(t,n,r){var e=r(1),i=r(30),o=r(16);e(e.S,"String",{raw:function(t){for(var n=i(t.raw),r=o(n.length),e=arguments.length,u=[],c=0;r>c;)u.push(String(n[c++])),c<e&&u.push(String(arguments[c]));return u.join("")}})},function(t,n,r){var e=r(1);e(e.P,"String",{repeat:r(149)})},function(t,n,r){"use strict";r(29)("small",function(t){return function(){return t(this,"small","","")}})},function(t,n,r){"use strict";var e=r(1),i=r(16),o=r(148),u="startsWith",c=""[u];e(e.P+e.F*r(134)(u),"String",{startsWith:function(t){var n=o(this,t,u),r=i(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),e=String(t);return c?c.call(n,e,r):n.slice(r,r+e.length)===e}})},function(t,n,r){"use strict";r(29)("strike",function(t){return function(){return t(this,"strike","","")}})},function(t,n,r){"use strict";r(29)("sub",function(t){return function(){return t(this,"sub","","")}})},function(t,n,r){"use strict";r(29)("sup",function(t){return function(){return t(this,"sup","","")}})},function(t,n,r){"use strict";r(82)("trim",function(t){return function(){return t(this,3)}})},function(t,n,r){"use strict";var e=r(3),i=r(24),o=r(10),u=r(1),c=r(28),f=r(65).KEY,a=r(4),s=r(126),l=r(81),h=r(76),v=r(7),p=r(182),d=r(153),y=r(206),g=r(205),b=r(138),m=r(2),x=r(30),w=r(50),S=r(66),_=r(70),O=r(174),E=r(31),P=r(11),j=r(72),F=E.f,M=P.f,A=O.f,N=e.Symbol,T=e.JSON,I=T&&T.stringify,k="prototype",L=v("_hidden"),R=v("toPrimitive"),C={}.propertyIsEnumerable,D=s("symbol-registry"),U=s("symbols"),W=s("op-symbols"),G=Object[k],B="function"==typeof N,V=e.QObject,z=!V||!V[k]||!V[k].findChild,q=o&&a(function(){return 7!=_(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=F(G,n);e&&delete G[n],M(t,n,r),e&&t!==G&&M(G,n,e)}:M,K=function(t){var n=U[t]=_(N[k]);return n._k=t,n},J=B&&"symbol"==typeof N.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof N},Y=function(t,n,r){return t===G&&Y(W,n,r),m(t),n=w(n,!0),m(r),i(U,n)?(r.enumerable?(i(t,L)&&t[L][n]&&(t[L][n]=!1),r=_(r,{enumerable:S(0,!1)})):(i(t,L)||M(t,L,S(1,{})),t[L][n]=!0),q(t,n,r)):M(t,n,r)},H=function(t,n){m(t);for(var r,e=g(n=x(n)),i=0,o=e.length;o>i;)Y(t,r=e[i++],n[r]);return t},$=function(t,n){return void 0===n?_(t):H(_(t),n)},X=function(t){var n=C.call(this,t=w(t,!0));return!(this===G&&i(U,t)&&!i(W,t))&&(!(n||!i(this,t)||!i(U,t)||i(this,L)&&this[L][t])||n)},Q=function(t,n){if(t=x(t),n=w(n,!0),t!==G||!i(U,n)||i(W,n)){var r=F(t,n);return!r||!i(U,n)||i(t,L)&&t[L][n]||(r.enumerable=!0),r}},Z=function(t){for(var n,r=A(x(t)),e=[],o=0;r.length>o;)i(U,n=r[o++])||n==L||n==f||e.push(n);return e},tt=function(t){for(var n,r=t===G,e=A(r?W:x(t)),o=[],u=0;e.length>u;)!i(U,n=e[u++])||r&&!i(G,n)||o.push(U[n]);return o};B||(N=function(){if(this instanceof N)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===G&&n.call(W,r),i(this,L)&&i(this[L],t)&&(this[L][t]=!1),q(this,t,S(1,r))};return o&&z&&q(G,t,{configurable:!0,set:n}),K(t)},c(N[k],"toString",function(){return this._k}),E.f=Q,P.f=Y,r(71).f=O.f=Z,r(116).f=X,r(125).f=tt,o&&!r(69)&&c(G,"propertyIsEnumerable",X,!0),p.f=function(t){return K(v(t))}),u(u.G+u.W+u.F*!B,{Symbol:N});for(var nt="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),rt=0;nt.length>rt;)v(nt[rt++]);for(var nt=j(v.store),rt=0;nt.length>rt;)d(nt[rt++]);u(u.S+u.F*!B,"Symbol",{for:function(t){return i(D,t+="")?D[t]:D[t]=N(t)},keyFor:function(t){if(J(t))return y(D,t);throw TypeError(t+" is not a symbol!")},useSetter:function(){z=!0},useSimple:function(){z=!1}}),u(u.S+u.F*!B,"Object",{create:$,defineProperty:Y,defineProperties:H,getOwnPropertyDescriptor:Q,getOwnPropertyNames:Z,getOwnPropertySymbols:tt}),T&&u(u.S+u.F*(!B||a(function(){var t=N();return"[null]"!=I([t])||"{}"!=I({a:t})||"{}"!=I(Object(t))})),"JSON",{stringify:function(t){if(void 0!==t&&!J(t)){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);return n=e[1],"function"==typeof n&&(r=n),!r&&b(n)||(n=function(t,n){if(r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,I.apply(T,e)}}}),N[k][R]||r(27)(N[k],R,N[k].valueOf),l(N,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){"use strict";var e=r(1),i=r(127),o=r(152),u=r(2),c=r(75),f=r(16),a=r(6),s=r(3).ArrayBuffer,l=r(146),h=o.ArrayBuffer,v=o.DataView,p=i.ABV&&s.isView,d=h.prototype.slice,y=i.VIEW,g="ArrayBuffer";e(e.G+e.W+e.F*(s!==h),{ArrayBuffer:h}),e(e.S+e.F*!i.CONSTR,g,{isView:function(t){return p&&p(t)||a(t)&&y in t}}),e(e.P+e.U+e.F*r(4)(function(){return!new h(2).slice(1,void 0).byteLength}),g,{slice:function(t,n){if(void 0!==d&&void 0===n)return d.call(u(this),t);for(var r=u(this).byteLength,e=c(t,r),i=c(void 0===n?r:n,r),o=new(l(this,h))(f(i-e)),a=new v(this),s=new v(o),p=0;e<i;)s.setUint8(p++,a.getUint8(e++));return o}}),r(74)(g)},function(t,n,r){var e=r(1);e(e.G+e.W+e.F*!r(127).ABV,{DataView:r(152).DataView})},function(t,n,r){r(55)("Float32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Float64",8,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Int16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Int32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Int8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Uint16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Uint32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}},!0)},function(t,n,r){"use strict";var e=r(166);r(118)("WeakSet",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(this,t,!0)}},e,!1,!0)},function(t,n,r){"use strict";var e=r(1),i=r(117)(!0);e(e.P,"Array",{includes:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(78)("includes")},function(t,n,r){var e=r(1),i=r(143)(),o=r(3).process,u="process"==r(45)(o);e(e.G,{asap:function(t){var n=u&&o.domain;i(n?n.bind(t):t)}})},function(t,n,r){var e=r(1),i=r(45);e(e.S,"Error",{isError:function(t){return"Error"===i(t)}})},function(t,n,r){var e=r(1);e(e.P+e.R,"Map",{toJSON:r(165)("Map")})},function(t,n,r){var e=r(1);e(e.S,"Math",{iaddh:function(t,n,r,e){var i=t>>>0,o=n>>>0,u=r>>>0;return o+(e>>>0)+((i&u|(i|u)&~(i+u>>>0))>>>31)|0}})},function(t,n,r){var e=r(1);e(e.S,"Math",{imulh:function(t,n){var r=65535,e=+t,i=+n,o=e&r,u=i&r,c=e>>16,f=i>>16,a=(c*u>>>0)+(o*u>>>16);return c*f+(a>>16)+((o*f>>>0)+(a&r)>>16)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{isubh:function(t,n,r,e){var i=t>>>0,o=n>>>0,u=r>>>0;return o-(e>>>0)-((~i&u|~(i^u)&i-u>>>0)>>>31)|0}})},function(t,n,r){var e=r(1);e(e.S,"Math",{umulh:function(t,n){var r=65535,e=+t,i=+n,o=e&r,u=i&r,c=e>>>16,f=i>>>16,a=(c*u>>>0)+(o*u>>>16);return c*f+(a>>>16)+((o*f>>>0)+(a&r)>>>16)}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(26),u=r(11);r(10)&&e(e.P+r(124),"Object",{__defineGetter__:function(t,n){u.f(i(this),t,{get:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(26),u=r(11);r(10)&&e(e.P+r(124),"Object",{__defineSetter__:function(t,n){u.f(i(this),t,{set:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){var e=r(1),i=r(176)(!0);e(e.S,"Object",{entries:function(t){return i(t)}})},function(t,n,r){var e=r(1),i=r(177),o=r(30),u=r(31),c=r(131);e(e.S,"Object",{getOwnPropertyDescriptors:function(t){for(var n,r=o(t),e=u.f,f=i(r),a={},s=0;f.length>s;)c(a,n=f[s++],e(r,n));return a}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(50),u=r(32),c=r(31).f;r(10)&&e(e.P+r(124),"Object",{__lookupGetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.get}while(r=u(r))}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(50),u=r(32),c=r(31).f;r(10)&&e(e.P+r(124),"Object",{__lookupSetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.set}while(r=u(r))}})},function(t,n,r){var e=r(1),i=r(176)(!1);e(e.S,"Object",{values:function(t){return i(t)}})},function(t,n,r){"use strict";var e=r(1),i=r(3),o=r(52),u=r(143)(),c=r(7)("observable"),f=r(26),a=r(2),s=r(68),l=r(73),h=r(27),v=r(79),p=v.RETURN,d=function(t){return null==t?void 0:f(t)},y=function(t){var n=t._c;n&&(t._c=void 0,n())},g=function(t){return void 0===t._o},b=function(t){g(t)||(t._o=void 0,y(t))},m=function(t,n){a(t),this._c=void 0,this._o=t,t=new x(this);try{var r=n(t),e=r;null!=r&&("function"==typeof r.unsubscribe?r=function(){e.unsubscribe()}:f(r),this._c=r)}catch(n){return void t.error(n)}g(this)&&y(this)};m.prototype=l({},{unsubscribe:function(){b(this)}});var x=function(t){this._s=t};x.prototype=l({},{next:function(t){var n=this._s;if(!g(n)){var r=n._o;try{var e=d(r.next);if(e)return e.call(r,t)}catch(t){try{b(n)}finally{throw t}}}},error:function(t){var n=this._s;if(g(n))throw t;var r=n._o;n._o=void 0;try{var e=d(r.error);if(!e)throw t;t=e.call(r,t)}catch(t){try{y(n)}finally{throw t}}return y(n),t},complete:function(t){var n=this._s;if(!g(n)){var r=n._o;n._o=void 0;try{var e=d(r.complete);t=e?e.call(r,t):void 0}catch(t){try{y(n)}finally{throw t}}return y(n),t}}});var w=function(t){s(this,w,"Observable","_f")._f=f(t)};l(w.prototype,{subscribe:function(t){return new m(t,this._f)},forEach:function(t){var n=this;return new(o.Promise||i.Promise)(function(r,e){f(t);var i=n.subscribe({next:function(n){try{return t(n)}catch(t){e(t),i.unsubscribe()}},error:e,complete:r})})}}),l(w,{from:function(t){var n="function"==typeof this?this:w,r=d(a(t)[c]);if(r){var e=a(r.call(t));return e.constructor===n?e:new n(function(t){return e.subscribe(t)})}return new n(function(n){var r=!1;return u(function(){if(!r){try{if(v(t,!1,function(t){if(n.next(t),r)return p})===p)return}catch(t){if(r)throw t;return void n.error(t)}n.complete()}}),function(){r=!0}})},of:function(){for(var t=0,n=arguments.length,r=Array(n);t<n;)r[t]=arguments[t++];return new("function"==typeof this?this:w)(function(t){var n=!1;return u(function(){if(!n){for(var e=0;e<r.length;++e)if(t.next(r[e]),n)return;t.complete()}}),function(){n=!0}})}}),h(w.prototype,c,function(){return this}),e(e.G,{Observable:w}),r(74)("Observable")},function(t,n,r){var e=r(54),i=r(2),o=e.key,u=e.set;e.exp({defineMetadata:function(t,n,r,e){u(t,n,i(r),o(e))}})},function(t,n,r){var e=r(54),i=r(2),o=e.key,u=e.map,c=e.store;e.exp({deleteMetadata:function(t,n){var r=arguments.length<3?void 0:o(arguments[2]),e=u(i(n),r,!1);if(void 0===e||!e.delete(t))return!1;if(e.size)return!0;var f=c.get(n);return f.delete(r),!!f.size||c.delete(n)}})},function(t,n,r){var e=r(185),i=r(161),o=r(54),u=r(2),c=r(32),f=o.keys,a=o.key,s=function(t,n){var r=f(t,n),o=c(t);if(null===o)return r;var u=s(o,n);return u.length?r.length?i(new e(r.concat(u))):u:r};o.exp({getMetadataKeys:function(t){return s(u(t),arguments.length<2?void 0:a(arguments[1]))}})},function(t,n,r){var e=r(54),i=r(2),o=r(32),u=e.has,c=e.get,f=e.key,a=function(t,n,r){if(u(t,n,r))return c(t,n,r);var e=o(n);return null!==e?a(t,e,r):void 0};e.exp({getMetadata:function(t,n){return a(t,i(n),arguments.length<3?void 0:f(arguments[2]))}})},function(t,n,r){var e=r(54),i=r(2),o=e.keys,u=e.key;e.exp({getOwnMetadataKeys:function(t){
return o(i(t),arguments.length<2?void 0:u(arguments[1]))}})},function(t,n,r){var e=r(54),i=r(2),o=e.get,u=e.key;e.exp({getOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(54),i=r(2),o=r(32),u=e.has,c=e.key,f=function(t,n,r){if(u(t,n,r))return!0;var e=o(n);return null!==e&&f(t,e,r)};e.exp({hasMetadata:function(t,n){return f(t,i(n),arguments.length<3?void 0:c(arguments[2]))}})},function(t,n,r){var e=r(54),i=r(2),o=e.has,u=e.key;e.exp({hasOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(54),i=r(2),o=r(26),u=e.key,c=e.set;e.exp({metadata:function(t,n){return function(r,e){c(t,n,(void 0!==e?i:o)(r),u(e))}}})},function(t,n,r){var e=r(1);e(e.P+e.R,"Set",{toJSON:r(165)("Set")})},function(t,n,r){"use strict";var e=r(1),i=r(147)(!0);e(e.P,"String",{at:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(1),i=r(46),o=r(16),u=r(122),c=r(120),f=RegExp.prototype,a=function(t,n){this._r=t,this._s=n};r(139)(a,"RegExp String",function(){var t=this._r.exec(this._s);return{value:t,done:null===t}}),e(e.P,"String",{matchAll:function(t){if(i(this),!u(t))throw TypeError(t+" is not a regexp!");var n=String(this),r="flags"in f?String(t.flags):c.call(t),e=new RegExp(t.source,~r.indexOf("g")?r:"g"+r);return e.lastIndex=o(t.lastIndex),new a(e,n)}})},function(t,n,r){"use strict";var e=r(1),i=r(181);e(e.P,"String",{padEnd:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!1)}})},function(t,n,r){"use strict";var e=r(1),i=r(181);e(e.P,"String",{padStart:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!0)}})},function(t,n,r){"use strict";r(82)("trimLeft",function(t){return function(){return t(this,1)}},"trimStart")},function(t,n,r){"use strict";r(82)("trimRight",function(t){return function(){return t(this,2)}},"trimEnd")},function(t,n,r){r(153)("asyncIterator")},function(t,n,r){r(153)("observable")},function(t,n,r){var e=r(1);e(e.S,"System",{global:r(3)})},function(t,n,r){for(var e=r(155),i=r(28),o=r(3),u=r(27),c=r(80),f=r(7),a=f("iterator"),s=f("toStringTag"),l=c.Array,h=["NodeList","DOMTokenList","MediaList","StyleSheetList","CSSRuleList"],v=0;v<5;v++){var p,d=h[v],y=o[d],g=y&&y.prototype;if(g){g[a]||u(g,a,l),g[s]||u(g,s,d),c[d]=l;for(p in e)g[p]||i(g,p,e[p],!0)}}},function(t,n,r){var e=r(1),i=r(151);e(e.G+e.B,{setImmediate:i.set,clearImmediate:i.clear})},function(t,n,r){var e=r(3),i=r(1),o=r(121),u=r(207),c=e.navigator,f=!!c&&/MSIE .\./.test(c.userAgent),a=function(t){return f?function(n,r){return t(o(u,[].slice.call(arguments,2),"function"==typeof n?n:Function(n)),r)}:t};i(i.G+i.B+i.F*f,{setTimeout:a(e.setTimeout),setInterval:a(e.setInterval)})},function(t,n,r){r(330),r(269),r(271),r(270),r(273),r(275),r(280),r(274),r(272),r(282),r(281),r(277),r(278),r(276),r(268),r(279),r(283),r(284),r(236),r(238),r(237),r(286),r(285),r(256),r(266),r(267),r(257),r(258),r(259),r(260),r(261),r(262),r(263),r(264),r(265),r(239),r(240),r(241),r(242),r(243),r(244),r(245),r(246),r(247),r(248),r(249),r(250),r(251),r(252),r(253),r(254),r(255),r(317),r(322),r(329),r(320),r(312),r(313),r(318),r(323),r(325),r(308),r(309),r(310),r(311),r(314),r(315),r(316),r(319),r(321),r(324),r(326),r(327),r(328),r(231),r(233),r(232),r(235),r(234),r(220),r(218),r(224),r(221),r(227),r(229),r(217),r(223),r(214),r(228),r(212),r(226),r(225),r(219),r(222),r(211),r(213),r(216),r(215),r(230),r(155),r(302),r(307),r(184),r(303),r(304),r(305),r(306),r(287),r(183),r(185),r(186),r(342),r(331),r(332),r(337),r(340),r(341),r(335),r(338),r(336),r(339),r(333),r(334),r(288),r(289),r(290),r(291),r(292),r(295),r(293),r(294),r(296),r(297),r(298),r(299),r(301),r(300),r(343),r(369),r(372),r(371),r(373),r(374),r(370),r(375),r(376),r(354),r(357),r(353),r(351),r(352),r(355),r(356),r(346),r(368),r(377),r(345),r(347),r(349),r(348),r(350),r(359),r(360),r(362),r(361),r(364),r(363),r(365),r(366),r(367),r(344),r(358),r(380),r(379),r(378),t.exports=r(52)},function(t,n){function r(t,n){if("string"==typeof n)return t.insertAdjacentHTML("afterend",n);var r=t.nextSibling;return r?t.parentNode.insertBefore(n,r):t.parentNode.appendChild(n)}t.exports=r},,,,,,,,,function(t,n,r){(function(n,r){!function(n){"use strict";function e(t,n,r,e){var i=n&&n.prototype instanceof o?n:o,u=Object.create(i.prototype),c=new p(e||[]);return u._invoke=s(t,r,c),u}function i(t,n,r){try{return{type:"normal",arg:t.call(n,r)}}catch(t){return{type:"throw",arg:t}}}function o(){}function u(){}function c(){}function f(t){["next","throw","return"].forEach(function(n){t[n]=function(t){return this._invoke(n,t)}})}function a(t){function n(r,e,o,u){var c=i(t[r],t,e);if("throw"!==c.type){var f=c.arg,a=f.value;return a&&"object"==typeof a&&m.call(a,"__await")?Promise.resolve(a.__await).then(function(t){n("next",t,o,u)},function(t){n("throw",t,o,u)}):Promise.resolve(a).then(function(t){f.value=t,o(f)},u)}u(c.arg)}function e(t,r){function e(){return new Promise(function(e,i){n(t,r,e,i)})}return o=o?o.then(e,e):e()}"object"==typeof r&&r.domain&&(n=r.domain.bind(n));var o;this._invoke=e}function s(t,n,r){var e=P;return function(o,u){if(e===F)throw new Error("Generator is already running");if(e===M){if("throw"===o)throw u;return y()}for(r.method=o,r.arg=u;;){var c=r.delegate;if(c){var f=l(c,r);if(f){if(f===A)continue;return f}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(e===P)throw e=M,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);e=F;var a=i(t,n,r);if("normal"===a.type){if(e=r.done?M:j,a.arg===A)continue;return{value:a.arg,done:r.done}}"throw"===a.type&&(e=M,r.method="throw",r.arg=a.arg)}}}function l(t,n){var r=t.iterator[n.method];if(r===g){if(n.delegate=null,"throw"===n.method){if(t.iterator.return&&(n.method="return",n.arg=g,l(t,n),"throw"===n.method))return A;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return A}var e=i(r,t.iterator,n.arg);if("throw"===e.type)return n.method="throw",n.arg=e.arg,n.delegate=null,A;var o=e.arg;return o?o.done?(n[t.resultName]=o.value,n.next=t.nextLoc,"return"!==n.method&&(n.method="next",n.arg=g),n.delegate=null,A):o:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,A)}function h(t){var n={tryLoc:t[0]};1 in t&&(n.catchLoc=t[1]),2 in t&&(n.finallyLoc=t[2],n.afterLoc=t[3]),this.tryEntries.push(n)}function v(t){var n=t.completion||{};n.type="normal",delete n.arg,t.completion=n}function p(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(h,this),this.reset(!0)}function d(t){if(t){var n=t[w];if(n)return n.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var r=-1,e=function n(){for(;++r<t.length;)if(m.call(t,r))return n.value=t[r],n.done=!1,n;return n.value=g,n.done=!0,n};return e.next=e}}return{next:y}}function y(){return{value:g,done:!0}}var g,b=Object.prototype,m=b.hasOwnProperty,x="function"==typeof Symbol?Symbol:{},w=x.iterator||"@@iterator",S=x.asyncIterator||"@@asyncIterator",_=x.toStringTag||"@@toStringTag",O="object"==typeof t,E=n.regeneratorRuntime;if(E)return void(O&&(t.exports=E));E=n.regeneratorRuntime=O?t.exports:{},E.wrap=e;var P="suspendedStart",j="suspendedYield",F="executing",M="completed",A={},N={};N[w]=function(){return this};var T=Object.getPrototypeOf,I=T&&T(T(d([])));I&&I!==b&&m.call(I,w)&&(N=I);var k=c.prototype=o.prototype=Object.create(N);u.prototype=k.constructor=c,c.constructor=u,c[_]=u.displayName="GeneratorFunction",E.isGeneratorFunction=function(t){var n="function"==typeof t&&t.constructor;return!!n&&(n===u||"GeneratorFunction"===(n.displayName||n.name))},E.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,c):(t.__proto__=c,_ in t||(t[_]="GeneratorFunction")),t.prototype=Object.create(k),t},E.awrap=function(t){return{__await:t}},f(a.prototype),a.prototype[S]=function(){return this},E.AsyncIterator=a,E.async=function(t,n,r,i){var o=new a(e(t,n,r,i));return E.isGeneratorFunction(n)?o:o.next().then(function(t){return t.done?t.value:o.next()})},f(k),k[_]="Generator",k.toString=function(){return"[object Generator]"},E.keys=function(t){var n=[];for(var r in t)n.push(r);return n.reverse(),function r(){for(;n.length;){var e=n.pop();if(e in t)return r.value=e,r.done=!1,r}return r.done=!0,r}},E.values=d,p.prototype={constructor:p,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=g,this.done=!1,this.delegate=null,this.method="next",this.arg=g,this.tryEntries.forEach(v),!t)for(var n in this)"t"===n.charAt(0)&&m.call(this,n)&&!isNaN(+n.slice(1))&&(this[n]=g)},stop:function(){this.done=!0;var t=this.tryEntries[0],n=t.completion;if("throw"===n.type)throw n.arg;return this.rval},dispatchException:function(t){function n(n,e){return o.type="throw",o.arg=t,r.next=n,e&&(r.method="next",r.arg=g),!!e}if(this.done)throw t;for(var r=this,e=this.tryEntries.length-1;e>=0;--e){var i=this.tryEntries[e],o=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var u=m.call(i,"catchLoc"),c=m.call(i,"finallyLoc");if(u&&c){if(this.prev<i.catchLoc)return n(i.catchLoc,!0);if(this.prev<i.finallyLoc)return n(i.finallyLoc)}else if(u){if(this.prev<i.catchLoc)return n(i.catchLoc,!0)}else{if(!c)throw new Error("try statement without catch or finally");if(this.prev<i.finallyLoc)return n(i.finallyLoc)}}}},abrupt:function(t,n){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e.tryLoc<=this.prev&&m.call(e,"finallyLoc")&&this.prev<e.finallyLoc){var i=e;break}}i&&("break"===t||"continue"===t)&&i.tryLoc<=n&&n<=i.finallyLoc&&(i=null);var o=i?i.completion:{};return o.type=t,o.arg=n,i?(this.method="next",this.next=i.finallyLoc,A):this.complete(o)},complete:function(t,n){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&n&&(this.next=n),A},finish:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),v(r),A}},catch:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc===t){var e=r.completion;if("throw"===e.type){var i=e.arg;v(r)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(t,n,r){return this.delegate={iterator:d(t),resultName:n,nextLoc:r},"next"===this.method&&(this.arg=g),A}}}("object"==typeof n?n:"object"==typeof window?window:"object"==typeof self?self:this)}).call(n,function(){return this}(),r(158))}])</script><script src="/blog/./main.0cf68a.js"></script><script>!function(){!function(e){var t=document.createElement("script");document.getElementsByTagName("body")[0].appendChild(t),t.setAttribute("src",e)}("/blog/slider.e37972.js")}()</script>


    
<div class="tools-col" q-class="show:isShow,hide:isShow|isFalse" q-on="click:stop(e)">
  <div class="tools-nav header-menu">
    
    
      
      
      
    
      
      
      
    
      
      
      
    
    

    <ul style="width: 70%">
    
    
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'innerArchive')"><a href="javascript:void(0)" target="_blank" rel="noopener" q-class="active:innerArchive">所有文章</a></li>
      
        
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'friends')"><a href="javascript:void(0)" target="_blank" rel="noopener" q-class="active:friends">友链</a></li>
      
        
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'aboutme')"><a href="javascript:void(0)" target="_blank" rel="noopener" q-class="active:aboutme">关于我</a></li>
      
        
    </ul>
  </div>
  <div class="tools-wrap">
    
    	<section class="tools-section tools-section-all" q-show="innerArchive">
        <div class="search-wrap">
          <input class="search-ipt" q-model="search" type="text" placeholder="find something…">
          <i class="icon-search icon" q-show="search|isEmptyStr"></i>
          <i class="icon-close icon" q-show="search|isNotEmptyStr" q-on="click:clearChose(e)"></i>
        </div>
        <div class="widget tagcloud search-tag">
          <p class="search-tag-wording">tag:</p>
          <label class="search-switch">
            <input type="checkbox" q-on="click:toggleTag(e)" q-attr="checked:showTags">
          </label>
          <ul class="article-tag-list" q-show="showTags">
            
            <div class="clearfix"></div>
          </ul>
        </div>
        <ul class="search-ul">
          <p q-show="jsonFail" style="padding: 20px; font-size: 12px;">
            缺失模块。<br/>1、请确保node版本大于6.2<br/>2、在博客根目录（注意不是yilia根目录）执行以下命令：<br/> npm i hexo-generator-json-content --save<br/><br/>
            3、在根目录_config.yml里添加配置：
<pre style="font-size: 12px;" q-show="jsonFail">
  jsonContent:
    meta: false
    pages: false
    posts:
      title: true
      date: true
      path: true
      text: false
      raw: false
      content: false
      slug: false
      updated: false
      comments: false
      link: false
      permalink: false
      excerpt: false
      categories: false
      tags: true
</pre>
          </p>
          <li class="search-li" q-repeat="items" q-show="isShow">
            <a q-attr="href:path|urlformat" class="search-title"><i class="icon-quo-left icon"></i><span q-text="title"></span></a>
            <p class="search-time">
              <i class="icon-calendar icon"></i>
              <span q-text="date|dateformat"></span>
            </p>
            <p class="search-tag">
              <i class="icon-price-tags icon"></i>
              <span q-repeat="tags" q-on="click:choseTag(e, name)" q-text="name|tagformat"></span>
            </p>
          </li>
        </ul>
    	</section>
    

    
    	<section class="tools-section tools-section-friends" q-show="friends">
  		
        <ul class="search-ul">
          
            <li class="search-li">
              <a href="http://localhost:4000/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>友情链接1</a>
            </li>
          
            <li class="search-li">
              <a href="http://localhost:4000/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>友情链接2</a>
            </li>
          
            <li class="search-li">
              <a href="http://localhost:4000/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>友情链接3</a>
            </li>
          
            <li class="search-li">
              <a href="http://localhost:4000/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>友情链接4</a>
            </li>
          
            <li class="search-li">
              <a href="http://localhost:4000/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>友情链接5</a>
            </li>
          
            <li class="search-li">
              <a href="http://localhost:4000/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>友情链接6</a>
            </li>
          
        </ul>
  		
    	</section>
    

    
    	<section class="tools-section tools-section-me" q-show="aboutme">
  	  	
  	  		<div class="aboutme-wrap" id="js-aboutme">很惭愧&lt;br&gt;&lt;br&gt;只做了一点微小的工作&lt;br&gt;谢谢大家</div>
  	  	
    	</section>
    
  </div>
  
</div>
    <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                      <div class="pswp__preloader__cut">
                        <div class="pswp__preloader__donut"></div>
                      </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div> 
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>
  </div>
</body>
</html>